Compare commits

...

75 Commits

Author SHA1 Message Date
John Preston
391b370b0f changed s|sg to start|startgroup in telegram.me links, version 0.8.27.dev 2015-06-17 23:29:58 +03:00
John Preston
9b863fcf5b version 0.8.27.dev ready 2015-06-17 23:07:25 +03:00
John Preston
609868858b Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-17 22:43:09 +03:00
John Preston
c291bf0861 version 0.8.27.dev ready 2015-06-17 22:43:03 +03:00
John Preston
bfd5482c15 langs updated 2015-06-17 22:42:44 +03:00
John Preston
52ea153c31 langs updated 2015-06-17 22:05:05 +03:00
John Preston
287c99ed2f 0.8.26.dev version with lang updates 2015-06-17 00:11:34 +03:00
John Preston
8e60f54dac Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-17 00:04:22 +03:00
John Preston
1f3fae53ca removed QCoreWlanEnginePlugin, some langs updates 2015-06-17 00:04:18 +03:00
John Preston
05ac97526e 0.8.26.dev version with fixed schema 2015-06-17 00:01:43 +03:00
John Preston
5fadfed47a test markup removed, version 0.8.25.dev ready 2015-06-15 20:48:25 +03:00
John Preston
ec1d547f27 warnings fixed, 0.8.25.dev code for os x and linux 2015-06-15 20:36:16 +03:00
John Preston
9fee0ace4c styles gen tab fixed, linux nautilus changed to xdg-open, source for 0.8.25.dev version 2015-06-15 20:30:02 +03:00
John Preston
84c2a33c18 0.8.25.dev version with ipv6, bots profiles, keyboard and command autocomplete + elided text align fixed 2015-06-15 20:19:24 +03:00
John Preston
83744e77d1 bot description displayed in message history, bot commands are highlighted and sent by click 2015-06-10 18:54:24 +03:00
John Preston
85635dbefd 31 layer, IPv6 support, bot support started 2015-06-10 15:48:26 +03:00
John Preston
7afda6dfc3 changelog added in 0.8.24 2015-06-08 15:25:23 +03:00
John Preston
3a35b6c5fd version 0.8.24 with new sticker packs buttons done 2015-06-08 15:24:21 +03:00
John Preston
c449e45def Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-08 15:19:42 +03:00
John Preston
6c704e3595 langs done 2015-06-08 15:19:25 +03:00
John Preston
318794692f trying to fix undefined behaviour in notifyShowNext() 2015-06-06 20:08:44 +03:00
John Preston
62a0878198 linear anim for sticker packs icons 2015-06-05 20:06:37 +03:00
John Preston
cb1c8c4aea version 0.8.23.dev with sticker packs buttons prepared 2015-06-05 18:59:58 +03:00
John Preston
1ee5c14b85 Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-05 18:55:58 +03:00
John Preston
01410e5d92 fixed crash in new sticker icons 2015-06-05 18:55:46 +03:00
John Preston
0e5d26a469 sticker packs buttons improved for os x 2015-06-05 18:37:11 +03:00
John Preston
95e5b7be0b stickers pan with packs buttons 2015-06-05 18:02:20 +03:00
John Preston
6d8f277904 version 0.8.22.dev prepared - new OpenAL version, new Auto-Update (no temp -> ready rename), some fixes 2015-06-04 16:04:34 +03:00
John Preston
b6325ec9d4 0.8.24.dev test (not production!) version ready, updaters improved (no tdata copying while updating) 2015-06-04 14:59:30 +03:00
John Preston
3373a2f382 skipping tdata folder when updating in linux, log file name fixed for Updater in os x 2015-06-04 14:51:52 +03:00
John Preston
9fcd878f1d Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-04 13:55:29 +03:00
John Preston
1c50c35abe 0.8.23.dev test (not production!) version ready, autoupdater cleaning old ready dir 2015-06-04 13:54:56 +03:00
John Preston
6a0ee57054 Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-04 13:52:29 +03:00
John Preston
00adfa6f3d improved logging of Updater in os x 2015-06-04 13:52:16 +03:00
John Preston
6058c8862e improved Updater in Linux (DebugLogs for Updater in working dir) 2015-06-04 13:50:53 +03:00
John Preston
e5f2f68188 fixed recording display, 0.8.22.dev test version ready (not production!) 2015-06-03 21:35:26 +03:00
John Preston
c6ee2772e2 new updater fixed for os x, 0.8.22.dev test version (not production) 2015-06-03 21:29:57 +03:00
John Preston
16caff1ca4 test dev version 0.8.22 (not production!) prepared, fixed display of group from which I left 2015-06-03 21:20:57 +03:00
John Preston
ac2ae16f47 MSVC instruction improved for OpenAL, merging autoupdate code for all OSs (not tested yet!) 2015-06-03 21:13:01 +03:00
John Preston
c40758f30d openal updated in Windows 2015-06-03 15:18:46 +03:00
John Preston
16aafe28d5 added minimum system version to Info.plist, added suspended flag to OpenAL handle 2015-06-03 14:57:14 +03:00
John Preston
db96605332 changelog added in 0.8.21 stable 2015-06-02 17:37:15 +03:00
John Preston
31954f5266 stable version 0.8.21 2015-06-02 17:31:25 +03:00
John Preston
66718de562 Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-02 17:01:13 +03:00
John Preston
e2d02f4e4b fixed gif animations on retina display 2015-06-02 17:00:30 +03:00
John Preston
e3d4bf192f 0.8.20.dev version linux local8bit -> utf8 2015-06-02 15:29:02 +03:00
John Preston
e7b94f3d3a version 0.8.20.dev prepared (some fixes) 2015-06-02 14:22:00 +03:00
John Preston
30be7af3e3 langs updated 2015-06-02 14:17:14 +03:00
John Preston
9069b4b269 langs updated for 0.8.19.dev 2015-06-01 23:40:09 +03:00
John Preston
040c80ae0b Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-01 23:29:28 +03:00
John Preston
a87c9b15d2 FixMake done for linux 32bit 2015-06-01 23:29:21 +03:00
John Preston
635cae4f94 version 0.8.19 with fixed stickerpacks and voice messages changelog 2015-06-01 23:24:09 +03:00
John Preston
67e6d12384 fixed build instructions 2015-06-01 15:56:55 +03:00
John Preston
9ccdc0e94b fixed build instructions 2015-06-01 15:52:44 +03:00
John Preston
762c0aa579 fixed build instructions 2015-06-01 15:49:53 +03:00
John Preston
5dc78932d7 instructions for building ffmpeg added 2015-06-01 15:44:10 +03:00
John Preston
e9c5f18142 fixed circle, final 0.8.18.dev-2 2015-06-01 14:21:41 +03:00
John Preston
175023b2dc Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-06-01 14:15:30 +03:00
John Preston
0a7c42c59a linux audio capture improved 2015-06-01 14:15:07 +03:00
John Preston
42122fdea0 added some stickers logging , 0.8.18 dev version ready 2015-06-01 13:58:46 +03:00
John Preston
4bfe65d8ab ffmpeg added to os x build 2015-06-01 12:42:56 +03:00
John Preston
1b06fe1220 ffmpeg audio play / capture done in os x 2015-05-30 19:30:47 +03:00
John Preston
1b11a7feae voice message recording / sending done 2015-05-29 21:52:43 +03:00
John Preston
6b60b51775 Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-05-26 11:19:52 +03:00
John Preston
981162e6b9 ffmpeg support for audio added 2015-05-26 11:19:47 +03:00
John Preston
c5dd99b1f1 added libmpg123 and libfaad to linux version 2015-05-24 21:58:42 +03:00
John Preston
53c536d76d libmpg123, libfaad and libmp4ff added to os x build 2015-05-24 21:39:07 +03:00
John Preston
e3ab8821b9 Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev 2015-05-24 20:59:04 +03:00
John Preston
6befea6a13 implemented .mp3 playing through libmpg123 and .m4a playing through libfaad2 in voice messages 2015-05-24 20:58:39 +03:00
John Preston
f24e3c6192 fixed audio mark as read after download 2015-05-23 18:57:16 +03:00
John Preston
63e593b3a8 fixed retina boxshadow 2015-05-22 14:43:47 +03:00
John Preston
7ca4ec1bed box shadows and bg changed, retina shadow draw fixed 2015-05-22 14:14:52 +03:00
John Preston
8ed1961886 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2015-05-21 18:09:07 +03:00
John Preston
962ec1e454 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2015-05-21 16:52:23 +03:00
John Preston
bcc718b5a9 QTKit.framework removed from build 2015-05-21 16:52:15 +03:00
111 changed files with 10055 additions and 7490 deletions

72
MSVC.md
View File

@@ -39,7 +39,7 @@ Extract to **D:\TBuild\Libraries**
http://www.zlib.net/ > Download [**zlib source code, version 1.2.8, zipfile format**](http://zlib.net/zlib128.zip)
Extract to **D:\TBuild\Libraries\**
Extract to **D:\\TBuild\\Libraries\\**
#####Building library
@@ -66,13 +66,23 @@ or download in ZIP and extract to **D:\TBuild\Libraries\**, rename **libexif-0.6
* Build Debug configuration
* Build Release configuration
####OpenAL Soft
####OpenAL Soft, slightly patched
Get sources by git in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries** and run
git clone git://repo.or.cz/openal-soft.git
to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**
to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**, then in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries/openal-soft** and run
git checkout 9479ea656b
Apply patch
* OR copy (with overwrite!) everything from **D:\\TBuild\\tdesktop\\\_openal\_patch\\** to **D:\\TBuild\\Libraries\\openal-soft\\**
* OR in Git Bash go to **/d/tbuild/libraries/openal-soft/** and run
git apply ./../../tdesktop/Telegram/_openal_patch.diff
#####Building library
@@ -87,35 +97,47 @@ to have **D:\TBuild\Libraries\openal-soft\CMakeLists.txt**
* OpenAL32 Properties > C/C++ > Code Generation > Runtime Library = **Multi-threaded (/MT)** **OK**
* common Properties > C/C++ > Code Generation > Runtime Library = **Multi-threaded (/MT)** **OK**
####libogg 1.3.2
Get sources from http://xiph.org/downloads/ in [ZIP](http://downloads.xiph.org/releases/ogg/libogg-1.3.2.zip) and extract to **D:\TBuild\Libraries\**
#####Building library
* Open in VS2013 **D:\TBuild\Libraries\libogg-1.3.2\win32\VS2010\libogg_static.sln** > One-way upgrade **OK**
* Build Debug configuration
* Build Release configuration
####Opus codec, opusfile
####Opus codec
Get sources by git in [Git Bash](http://git-scm.com/downloads) go to **/d/tbuild/libraries** and run
git clone git://git.opus-codec.org/opus.git
git clone git://git.xiph.org/opusfile.git
git clone https://github.com/telegramdesktop/opus.git
to have **D:\TBuild\Libraries\opus\win32**
#####Building libraries
* Open in VS2013 **D:\TBuild\Libraries\opus\win32\VS2010\opus.sln** > One-way upgrade **OK**
* Open in VS2013 **D:\TBuild\Libraries\opus\win32\VS2010\opus.sln**
* Build Debug configuration
* Build Release configuration
* Open in VS2013 **D:\TBuild\Libraries\opusfile\win32\VS2010\opusfile.sln** > One-way upgrade **OK**
* For **Debug** and **Release** configurations
* opusfile > C/C++ > General > Additional include directories > Add **../../../libogg-1.3.2/include;**
* Build Debug configuration
* Build Release configuration
* Build Release configuration (it will be required in **FFmpeg** build!)
####FFmpeg
https://www.ffmpeg.org/download.html > Download [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2)
Extract to **D:\\TBuild\\Libraries**
http://msys2.github.io/ > Download [msys2-x86_64-20150512.exe](http://sourceforge.net/projects/msys2/files/Base/x86_64/msys2-x86_64-20150512.exe/download) and install to **D:\\msys64**
#####Building libraries
Download [yasm for Win64](http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe) from http://yasm.tortall.net/Download.html, rename **yasm-1.3.0-win64.exe** to **yasm.exe** and place it to your Visual C++ **bin** directory, like **\\Program Files (x86)\\Microsoft Visual Studio 12\\VC\\bin\\**
Open **VS2013 x86 Native Tools Command Prompt.bat** (should be in **\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Common7\\Tools\\Shortcuts\\** folder), go to **D:\\msys64\\** and launch **msys2_shell.bat**, there run
PATH="/c/Program Files (x86)/Microsoft Visual Studio 12.0/VC/BIN:$PATH"
cd /d/TBuild/Libraries/ffmpeg-2.6.3
pacman -S msys/make
pacman -S mingw64/mingw-w64-x86_64-opus
pacman -S pkg-config
PKG_CONFIG_PATH="/mingw64/lib/pkgconfig:$PKG_CONFIG_PATH"
./configure --toolchain=msvc --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-muxer=ogg --enable-muxer=opus --extra-ldflags="-libpath:/d/TBuild/Libraries/opus/win32/VS2010/Win32/Release celt.lib silk_common.lib silk_float.lib"
make
make install
####Qt 5.4.0, slightly patched
@@ -126,9 +148,9 @@ Extract to **D:\TBuild\Libraries\**, rename **qt-everywhere-opensource-src-5.4.0
Apply patch
* OR copy (with overwrite!) everything from **D:\TBuild\tdesktop\\\_qt\_5\_4\_0\_patch\** to **D:\TBuild\Libraries\QtStatic\**
* OR copy **D:\TBuild\tdesktop\\\_qt\_5\_4\_0\_patch.diff** to **D:\TBuild\Libraries\QtStatic\**, go there in Git Bash and run
* OR in Git Bash go to **/d/TBuild/Libraries/QtStatic/** and run
git apply _qt_5_4_0_patch.diff
git apply ./../../tdesktop/Telegram/_qt_5_4_0_patch.diff
#####Building library

View File

@@ -34,27 +34,24 @@ Install dev libraries
Install audio libraries
####libogg-1.3.2
[Download libogg-1.3.2 sources](http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.xz) from http://xiph.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/libogg-1.3.2** and run
./configure
make
sudo make install
####Opus codec 1.1
[Download opus-1.1 sources](http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz) from http://www.opus-codec.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/opus-1.1** and run
Download [opus-1.1 sources](http://downloads.xiph.org/releases/opus/opus-1.1.tar.gz) from http://www.opus-codec.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/opus-1.1** and run
./configure
make
sudo make install
####opusfile-0.6
####FFmpeg
[Download opusfile-0.6 sources](http://downloads.xiph.org/releases/opus/opusfile-0.6.tar.gz) from http://www.opus-codec.org/downloads, extract to **/home/user/TBuild/Libraries**, go to **/home/user/TBuild/Libraries/opusfile-0.6** and run
Download sources [ffmpeg-2.6.3.tar.bz2](http://ffmpeg.org/releases/ffmpeg-2.6.3.tar.bz2) from https://www.ffmpeg.org/download.html, extract to **/home/user/TBuild/Libraries** to have **/home/user/TBuild/Libraries/ffmpeg-2.6.3**, go to **/home/user/TBuild/Libraries/ffmpeg-2.6.3** and run
sudo apt-get update
sudo apt-get -y --force-yes install autoconf automake build-essential libass-dev libfreetype6-dev libgpac-dev libsdl1.2-dev libtheora-dev libtool libva-dev libvdpau-dev libvorbis-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev pkg-config texi2html zlib1g-dev
sudo apt-get install yasm
./configure --prefix=/usr/local --disable-programs --disable-everything --enable-libopus --enable-decoder=aac --enable-decoder=aac_latm --enable-decoder=aasc --enable-decoder=mp1 --enable-decoder=mp1float --enable-decoder=mp2 --enable-decoder=mp2float --enable-decoder=mp3 --enable-decoder=mp3adu --enable-decoder=mp3adufloat --enable-decoder=mp3float --enable-decoder=mp3on4 --enable-decoder=mp3on4float --enable-decoder=wavpack --enable-decoder=opus --enable-decoder=vorbis --enable-decoder=wmalossless --enable-decoder=wmapro --enable-decoder=wmav1 --enable-decoder=wmav2 --enable-decoder=wmavoice --enable-encoder=libopus --enable-parser=aac --enable-parser=aac_latm --enable-parser=mpegaudio --enable-parser=opus --enable-parser=vorbis --enable-demuxer=aac --enable-demuxer=wav --enable-demuxer=mp3 --enable-demuxer=ogg --enable-demuxer=mov --enable-muxer=ogg --enable-muxer=opus
./configure
make
sudo make install
@@ -72,7 +69,7 @@ In Terminal go to **/home/user/TBuild/Libraries** and run
git clone git://repo.or.cz/openal-soft.git
then go to **/home/user/TBuild/Libraries/openal-soft/build** and run
then go to **/home/user/TBuild/Libraries/openal-soft/build** and run
sudo apt-get install cmake
cmake -D LIBTYPE:STRING=STATIC ..

View File

@@ -6,17 +6,9 @@ Source code is published under GPL v3, license is available [here](https://githu
###Supported systems
* Windows XP
* Windows Vista
* Windows 7
* Windows 8 (**not** RT)
* Windows 8.1 (**not** RT)
* OS X 10.7
* OS X 10.8
* OS X 10.9
* Ubuntu 12.04
* Ubuntu 13.04
* Ubuntu 14.04
* Windows XP - Windows 8.1 (**not** RT)
* Mac OS X 10.7 - Mac OS X 10.10
* Ubuntu 12.04 - Ubuntu 14.04
###Third-party
@@ -27,15 +19,15 @@ Source code is published under GPL v3, license is available [here](https://githu
* LZMA SDK 9.20 ([public domain](http://www.7-zip.org/sdk.html))
* liblzma ([public domain](http://tukaani.org/xz/))
* OpenAL Soft ([LGPL](http://kcat.strangesoft.net/openal.html))
* Opus codec, opusfile ([BSD license](http://www.opus-codec.org/license/))
* libogg ([BSD license](http://www.xiph.org/downloads/))
* Opus codec ([BSD license](http://www.opus-codec.org/license/))
* FFmpeg ([LGPL](https://www.ffmpeg.org/legal.html))
* Open Sans font ([Apache License](http://www.apache.org/licenses/LICENSE-2.0.html))
###[Build instructions for Visual Studio 2013](https://github.com/telegramdesktop/tdesktop/blob/master/MSVC.md)
###[Build instructions for XCode 5.1.1](https://github.com/telegramdesktop/tdesktop/blob/master/XCODE.md)
###[Build instructions for XCode 6.3.1](https://github.com/telegramdesktop/tdesktop/blob/master/XCODE.md)
###[Build instructions for Qt Creator 3.1.2 Ubuntu](https://github.com/telegramdesktop/tdesktop/blob/master/QTCREATOR.md)
###[Build instructions for Qt Creator 3.2.0 Ubuntu](https://github.com/telegramdesktop/tdesktop/blob/master/QTCREATOR.md)
##Projects in Telegram solution

View File

@@ -23,7 +23,9 @@ Replace '\-lfontconfig' '\/usr\/lib\/x86_64\-linux\-gnu\/libfontconfig\.a \/usr\
Replace '\-lfreetype' '\/usr\/lib\/x86_64\-linux\-gnu\/libfreetype\.a'
Replace '\-lpng' '\/usr\/lib\/x86_64\-linux\-gnu\/libpng\.a'
Replace '\-lXext' '\/usr\/lib\/x86_64\-linux\-gnu\/libXext\.a'
Replace '\-lopusfile' '\/usr\/local\/lib\/libopusfile\.a'
Replace '\-lopus' '\/usr\/local\/lib\/libopus\.a'
Replace '\-lopenal' '\/usr\/local\/lib\/libopenal\.a'
Replace '\-logg' '\/usr\/local\/lib\/libogg\.a'
Replace '\-lavformat' '\/usr\/local\/lib\/libavformat\.a'
Replace '\-lavcodec' '\/usr\/local\/lib\/libavcodec\.a'
Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a'
Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a'

View File

@@ -23,7 +23,9 @@ Replace '\-lfontconfig' '\/usr\/lib\/i386\-linux\-gnu\/libfontconfig\.a \/usr\/l
Replace '\-lfreetype' '\/usr\/lib\/i386\-linux\-gnu\/libfreetype\.a'
Replace '\-lpng' '\/usr\/lib\/i386\-linux\-gnu\/libpng\.a'
Replace '\-lXext' '\/usr\/lib\/i386\-linux\-gnu\/libXext\.a'
Replace '\-lopusfile' '\/usr\/local\/lib\/libopusfile\.a'
Replace '\-lopus' '\/usr\/local\/lib\/libopus\.a'
Replace '\-lopenal' '\/usr\/local\/lib\/libopenal\.a'
Replace '\-logg' '\/usr\/local\/lib\/libogg\.a'
Replace '\-lavformat' '\/usr\/local\/lib\/libavformat\.a'
Replace '\-lavcodec' '\/usr\/local\/lib\/libavcodec\.a'
Replace '\-lswresample' '\/usr\/local\/lib\/libswresample\.a'
Replace '\-lavutil' '\/usr\/local\/lib\/libavutil\.a'

View File

@@ -1,10 +1,10 @@
@echo OFF
set "AppVersion=8017"
set "AppVersionStrSmall=0.8.17"
set "AppVersionStr=0.8.17"
set "AppVersionStrFull=0.8.17.0"
set "DevChannel=0"
set "AppVersion=8027"
set "AppVersionStrSmall=0.8.27"
set "AppVersionStr=0.8.27"
set "AppVersionStrFull=0.8.27.0"
set "DevChannel=1"
if %DevChannel% neq 0 goto preparedev

View File

@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "Try now";
"lng_status_service_notifications" = "service notifications";
"lng_status_bot" = "bot";
"lng_status_bot_reads_all" = "has access to messages";
"lng_status_bot_not_reads_all" = "has no access to messages";
"lng_status_offline" = "last seen a long time ago";
"lng_status_recently" = "last seen recently";
"lng_status_last_week" = "last seen within a week";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "Group is unaccessible";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "About";
"lng_profile_settings_section" = "Settings";
"lng_profile_bot_settings" = "Settings";
"lng_profile_bot_help" = "Help";
"lng_profile_participants_section" = "Members";
"lng_profile_info" = "Contact info";
"lng_profile_group_info" = "Group info";
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_clear_history" = "Clear history";
"lng_profile_send_message" = "Send Message";
"lng_profile_share_contact" = "Share Contact";
"lng_profile_invite_to_group" = "Add to Group";
"lng_profile_delete_contact" = "Delete";
"lng_profile_set_group_photo" = "Set Photo";
"lng_profile_add_participant" = "Add Member";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "Send";
"lng_message_ph" = "Write a message..";
"lng_record_cancel" = "Release outside this field to cancel";
"lng_empty_history" = "";
"lng_willbe_history" = "Please select chat to start messaging";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "You";
"lng_bot_description" = "What can this bot do?";
"lng_bot_start" = "Start";
"lng_bot_choose_group" = "Choose Group";
"lng_bot_no_groups" = "You have no groups";
"lng_bot_groups_not_found" = "No groups found";
"lng_bot_sure_invite" = "Add the bot to «{group}»?";
"lng_bot_already_in_group" = "The bot is already a member of the group.";
"lng_typing" = "typing";
"lng_user_typing" = "{user} is typing";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}";
"lng_new_version_minor" = "— Bug fixes and other minor improvements";
"lng_new_version_text" = "— Added support for sticker packs\n— New emoji and sticker panel";
"lng_new_version_text" = "This new version includes support for bots using the new bot API, free for everyone. If you're an engineer, create your own bots for games, services or integrations.\n\nLearn more at {blog_link}";
"lng_menu_insert_unicode" = "Insert Unicode control character";

View File

@@ -54,7 +54,7 @@ wndBG: #FFF;
wndShadow: sprite(209px, 46px, 19px, 19px);
wndShadowShift: 1px;
layerAlpha: 0.3;
layerAlpha: 0.5;
layerBG: #000;
titleBG: #6389a8;
@@ -390,7 +390,8 @@ btnIntroNext: flatButton(btnDefNext, btnDefBig) {
width: 300px;
}
boxShadow: sprite(230px, 46px, 9px, 9px);
boxShadow: sprite(363px, 50px, 15px, 15px);
boxShadowShift: 2px;
introCountry: countryInput {
width: 300px;
@@ -776,7 +777,7 @@ activeFadeOutDuration: 3000;
msgRadius: 3px;
msgMaxWidth: 550px;
msgMaxWidth: 430px;
msgFont: font(fsize);
msgNameFont: font(fsize semibold);
msgServiceFont: font(fsize semibold);
@@ -985,12 +986,33 @@ btnAttachPhoto: iconedButton(btnAttachDocument) {
btnAttachEmoji: iconedButton(btnAttachDocument) {
overBgColor: white;
icon: sprite(363px, 344px, 21px, 22px);
iconPos: point(6px, 13px);
iconPos: point(6px, 12px);
downIcon: sprite(363px, 344px, 21px, 22px);
downIconPos: point(6px, 13px);
downIconPos: point(6px, 12px);
width: 33px;
}
btnBotKbShow: iconedButton(btnAttachEmoji) {
icon: sprite(375px, 74px, 21px, 16px);
iconPos: point(6px, 16px);
downIcon: sprite(375px, 74px, 21px, 16px);
downIconPos: point(6px, 16px);
}
btnBotKbHide: iconedButton(btnAttachEmoji) {
icon: sprite(352px, 74px, 23px, 14px);
iconPos: point(5px, 17px);
downIcon: sprite(352px, 74px, 23px, 14px);
downIconPos: point(5px, 17px);
}
btnRecordAudio: sprite(363px, 366px, 16px, 24px);
btnRecordAudioActive: sprite(379px, 366px, 16px, 24px);
recordSignalColor: #f17077;
recordSignalMin: 5px;
recordSignalMax: 10px;
recordCancel: #aaa;
recordCancelActive: #ec6466;
recordFont: font(13px);
recordTextTop: 14px;
replySkip: 51px;
replyColor: #377aae;
@@ -1031,7 +1053,7 @@ textRectMargins: margins(-2px, -1px, -2px, -1px);
taMsgField: flatTextarea(taDefFlat) {
font: msgFont;
}
maxFieldHeight: 250px;
maxFieldHeight: 265px;
newMsgSound: ':/gui/art/newmsg.wav';
@@ -1175,6 +1197,7 @@ btnShareContact: flatButton(btnDefNext, btnDefBig) {
font: font(17px);
overFont: font(17px);
}
profileMinBtnPadding: 10px;
forwardWidth: 364px;
forwardMargins: margins(30px, 10px, 30px, 10px);
@@ -1375,6 +1398,7 @@ dropdownDef: dropdown {
padding: margins(10px, 10px, 10px, 10px);
shadow: sprite(241px, 46px, 6px, 6px);
shadowShift: 1px;
duration: 150;
width: 0px;
@@ -1639,6 +1663,38 @@ stickerPanSize: size(55px, 55px);
stickerPanPadding: 11px;
stickerPanDelete: sprite(123px, 132px, 12px, 12px);
stickerPanDeleteOpacity: 0.5;
stickerIconPadding: 3px;
stickerIconHover: #e8ecef;
stickerIconSel: #dfe3e6;
stickerIconRecent: sprite(342px, 50px, 21px, 22px);
stickerIconLeft: sprite(342px, 72px, 40px, 1px);
stickerIconRight: sprite(342px, 73px, 40px, 1px);
stickerIconMove: 400;
botKbDuration: 200;
botKbBg: #f7f7f7;
botKbOverBg: #e8ecef;
botKbDownBg: #dfe3e6;
botKbColor: #8a8a8f;
botKbFont: font(16px);
botKbButton: botKeyboardButton {
margin: 10px;
padding: 10px;
height: 46px;
textTop: 13px;
downTextTop: 14px;
}
botKbTinyButton: botKeyboardButton {
margin: 4px;
padding: 3px;
height: 25px;
textTop: 2px;
downTextTop: 3px;
}
botKbScroll: flatScroll(newScroll) {
deltax: 3px;
width: 10px;
}
mvBgColor: #222;
mvBgOpacity: 0.92;
@@ -1715,9 +1771,13 @@ mvDocExtColor: white;
mvDocExtPadding: 10px;
mvDocLinksTop: 57px;
mvDocRed: sprite(0px, 400px, 80px, 80px);
mvDocRedColor: #e47272;
mvDocYellow: sprite(80px, 400px, 80px, 80px);
mvDocYellowColor: #efc274;
mvDocGreen: sprite(160px, 400px, 80px, 80px);
mvDocGreenColor: #61b96e;
mvDocBlue: sprite(240px, 400px, 80px, 80px);
mvDocBlueColor: #72b1df;
mvDocLink: linkButton(btnDefLink) {
color: #4595d3;
overColor: #4595d3;
@@ -1796,7 +1856,7 @@ radialBgOpacity: 0.4;
radialDownload: sprite(346px, 0px, 50px, 50px);
radialDownloadOpacity: 0.8;
radialCancel: sprite(378px, 50px, 18px, 18px);
radialCancelOpacity: 0.7;
radialCancelOpacity: 1.0;
overviewLoader: size(34px, 14px);
overviewLoaderPoint: size(4px, 4px);
@@ -1865,6 +1925,8 @@ mentionPadding: margins(8px, 5px, 8px, 5px);
mentionTop: 11px;
mentionFont: linkFont;
mentionPhotoSize: msgPhotoSize;
botCommandFont: font(fsize semibold);
botDescFont: font(fsize italic);
sessionsHeight: 440px;
sessionHeight: 70px;
@@ -1893,3 +1955,5 @@ webPageDescriptionFont: font(fsize);
webPagePhotoSkip: 5px;
webPagePhotoSize: 100px;
webPagePhotoDelta: 8px;
botDescSkip: 8px;

View File

@@ -254,7 +254,16 @@ dropdown {
padding: margins;
shadow: sprite;
shadowShift: number;
duration: number;
width: number;
}
botKeyboardButton {
margin: number;
padding: number;
height: number;
textTop: number;
downTextTop: number;
}

View File

@@ -1574,7 +1574,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\
tcpp << "\tFontFamilies _fontFamilies;\n";
tcpp << "\tFontDatas _fontsMap;\n";
tcpp << "\tColorDatas _colorsMap;\n";
tcpp << "int _spriteWidth = " << spriteWidths[0] << ";\n\n";
tcpp << "\tint _spriteWidth = " << spriteWidths[0] << ";\n\n";
tcpp << "\tvoid startManager() {\n";
tcpp << "\n\t\tif (cRetina()) {\n";

View File

@@ -96,12 +96,11 @@ void writeLog(const wstring &msg) {
}
}
void delFolder() {
wstring delPath = L"tupdates\\ready", delFolder = L"tupdates";
void fullClearPath(const wstring &dir) {
WCHAR path[4096];
memcpy(path, delPath.c_str(), (delPath.size() + 1) * sizeof(WCHAR));
path[delPath.size() + 1] = 0;
writeLog(L"Fully clearing path '" + delPath + L"'..");
memcpy(path, dir.c_str(), (dir.size() + 1) * sizeof(WCHAR));
path[dir.size() + 1] = 0;
writeLog(L"Fully clearing path '" + dir + L"'..");
SHFILEOPSTRUCT file_op = {
NULL,
FO_DELETE,
@@ -116,13 +115,46 @@ void delFolder() {
};
int res = SHFileOperation(&file_op);
if (res) writeLog(L"Error: failed to clear path! :(");
}
void delFolder() {
wstring delPathOld = L"tupdates\\ready", delPath = L"tupdates\\temp", delFolder = L"tupdates";
fullClearPath(delPathOld);
fullClearPath(delPath);
RemoveDirectory(delFolder.c_str());
}
DWORD versionNum = 0, versionLen = 0, readLen = 0;
WCHAR versionStr[32] = { 0 };
bool update() {
writeLog(L"Update started..");
wstring updDir = L"tupdates\\ready";
wstring updDir = L"tupdates\\temp", readyFilePath = L"tupdates\\temp\\ready", tdataDir = L"tupdates\\temp\\tdata";
{
HANDLE readyFile = CreateFile(readyFilePath.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (readyFile != INVALID_HANDLE_VALUE) {
CloseHandle(readyFile);
} else {
updDir = L"tupdates\\ready"; // old
tdataDir = L"tupdates\\ready\\tdata";
}
}
HANDLE versionFile = CreateFile((tdataDir + L"\\version").c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (versionFile != INVALID_HANDLE_VALUE) {
if (ReadFile(versionFile, &versionNum, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD)) {
versionNum = 0;
} else if (ReadFile(versionFile, &versionLen, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD) || versionLen > 63) {
versionNum = 0;
} else if (ReadFile(versionFile, versionStr, versionLen, &readLen, NULL) != TRUE || readLen != versionLen) {
versionNum = 0;
}
CloseHandle(versionFile);
writeLog(L"Version file read.");
} else {
writeLog(L"Could not open version file to update registry :(");
}
deque<wstring> dirs;
dirs.push_back(updDir);
@@ -154,22 +186,27 @@ bool update() {
}
do {
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
wstring fname = dir + L"\\" + findData.cFileName;
if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) {
writeLog(L"Skipped 'tdata' path '" + fname + L"'");
} else if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (findData.cFileName != wstring(L".") && findData.cFileName != wstring(L"..")) {
dirs.push_back(dir + L"\\" + findData.cFileName);
writeLog(L"Added dir '" + dir + L"\\" + findData.cFileName + L"' in update tree..");
dirs.push_back(fname);
writeLog(L"Added dir '" + fname + L"' in update tree..");
}
} else {
wstring fname = dir + L"\\" + findData.cFileName;
wstring tofname = updateTo + fname.substr(updDir.size() + 1);
if (equal(tofname, exeName)) { // bad update - has Updater.exe - delete all dir
writeLog(L"Error: bad update, has Updater.exe! '" + tofname + L"' equal '" + exeName + L"'");
delFolder();
return false;
} else if (equal(fname, readyFilePath)) {
writeLog(L"Skipped ready file '" + fname + L"'");
} else {
from.push_back(fname);
to.push_back(tofname);
writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'");
}
from.push_back(fname);
to.push_back(tofname);
writeLog(L"Added file '" + fname + L"' to be copied to '" + tofname + L"'");
}
} while (FindNextFile(findHandle, &findData));
DWORD errorCode = GetLastError();
@@ -230,73 +267,57 @@ bool update() {
}
void updateRegistry() {
HANDLE versionFile = CreateFile((updateTo + L"tdata\\version").c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (versionFile != INVALID_HANDLE_VALUE) {
if (versionNum) {
writeLog(L"Updating registry..");
DWORD versionNum = 0, versionLen = 0, readLen = 0;
WCHAR versionStr[32];
if (ReadFile(versionFile, &versionNum, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD)) {
versionNum = 0;
} else if (ReadFile(versionFile, &versionLen, sizeof(DWORD), &readLen, NULL) != TRUE || readLen != sizeof(DWORD) || versionLen > 63) {
versionNum = 0;
} else if (ReadFile(versionFile, versionStr, versionLen, &readLen, NULL) != TRUE || readLen != versionLen) {
versionNum = 0;
}
CloseHandle(versionFile);
writeLog(L"Version file read.");
if (versionNum) {
versionStr[versionLen / 2] = 0;
HKEY rkey;
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1", 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &rkey);
if (status == ERROR_SUCCESS) {
writeLog(L"Checking registry install location..");
static const int bufSize = 4096;
DWORD locationType, locationSize = bufSize * 2;
WCHAR locationStr[bufSize], exp[bufSize];
if (RegQueryValueEx(rkey, L"InstallLocation", 0, &locationType, (BYTE*)locationStr, &locationSize) == ERROR_SUCCESS) {
locationSize /= 2;
if (locationStr[locationSize - 1]) {
locationStr[locationSize++] = 0;
versionStr[versionLen / 2] = 0;
HKEY rkey;
LSTATUS status = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{53F49750-6209-4FBF-9CA8-7A333C87D1ED}_is1", 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &rkey);
if (status == ERROR_SUCCESS) {
writeLog(L"Checking registry install location..");
static const int bufSize = 4096;
DWORD locationType, locationSize = bufSize * 2;
WCHAR locationStr[bufSize], exp[bufSize];
if (RegQueryValueEx(rkey, L"InstallLocation", 0, &locationType, (BYTE*)locationStr, &locationSize) == ERROR_SUCCESS) {
locationSize /= 2;
if (locationStr[locationSize - 1]) {
locationStr[locationSize++] = 0;
}
if (locationType == REG_EXPAND_SZ) {
DWORD copy = ExpandEnvironmentStrings(locationStr, exp, bufSize);
if (copy <= bufSize) {
memcpy(locationStr, exp, copy * sizeof(WCHAR));
}
if (locationType == REG_EXPAND_SZ) {
DWORD copy = ExpandEnvironmentStrings(locationStr, exp, bufSize);
if (copy <= bufSize) {
memcpy(locationStr, exp, copy * sizeof(WCHAR));
}
}
if (locationType == REG_EXPAND_SZ || locationType == REG_SZ) {
if (PathCanonicalize(exp, locationStr) == TRUE) {
memcpy(locationStr, exp, bufSize * sizeof(WCHAR));
if (GetFullPathName(L".", bufSize, exp, 0) < bufSize) {
wstring installpath = locationStr, mypath = exp;
if (installpath == mypath + L"\\" || true) { // always update reg info, if we found it
WCHAR nameStr[bufSize], dateStr[bufSize], publisherStr[bufSize], icongroupStr[bufSize];
SYSTEMTIME stLocalTime;
GetLocalTime(&stLocalTime);
RegSetValueEx(rkey, L"DisplayVersion", 0, REG_SZ, (BYTE*)versionStr, ((versionLen / 2) + 1) * sizeof(WCHAR));
wsprintf(nameStr, L"Telegram Desktop version %s", versionStr);
RegSetValueEx(rkey, L"DisplayName", 0, REG_SZ, (BYTE*)nameStr, (wcslen(nameStr) + 1) * sizeof(WCHAR));
wsprintf(publisherStr, L"Telegram Messenger LLP");
RegSetValueEx(rkey, L"Publisher", 0, REG_SZ, (BYTE*)publisherStr, (wcslen(publisherStr) + 1) * sizeof(WCHAR));
wsprintf(icongroupStr, L"Telegram Desktop");
RegSetValueEx(rkey, L"Inno Setup: Icon Group", 0, REG_SZ, (BYTE*)icongroupStr, (wcslen(icongroupStr) + 1) * sizeof(WCHAR));
wsprintf(dateStr, L"%04d%02d%02d", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay);
RegSetValueEx(rkey, L"InstallDate", 0, REG_SZ, (BYTE*)dateStr, (wcslen(dateStr) + 1) * sizeof(WCHAR));
}
if (locationType == REG_EXPAND_SZ || locationType == REG_SZ) {
if (PathCanonicalize(exp, locationStr) == TRUE) {
memcpy(locationStr, exp, bufSize * sizeof(WCHAR));
if (GetFullPathName(L".", bufSize, exp, 0) < bufSize) {
wstring installpath = locationStr, mypath = exp;
if (installpath == mypath + L"\\" || true) { // always update reg info, if we found it
WCHAR nameStr[bufSize], dateStr[bufSize], publisherStr[bufSize], icongroupStr[bufSize];
SYSTEMTIME stLocalTime;
GetLocalTime(&stLocalTime);
RegSetValueEx(rkey, L"DisplayVersion", 0, REG_SZ, (BYTE*)versionStr, ((versionLen / 2) + 1) * sizeof(WCHAR));
wsprintf(nameStr, L"Telegram Desktop version %s", versionStr);
RegSetValueEx(rkey, L"DisplayName", 0, REG_SZ, (BYTE*)nameStr, (wcslen(nameStr) + 1) * sizeof(WCHAR));
wsprintf(publisherStr, L"Telegram Messenger LLP");
RegSetValueEx(rkey, L"Publisher", 0, REG_SZ, (BYTE*)publisherStr, (wcslen(publisherStr) + 1) * sizeof(WCHAR));
wsprintf(icongroupStr, L"Telegram Desktop");
RegSetValueEx(rkey, L"Inno Setup: Icon Group", 0, REG_SZ, (BYTE*)icongroupStr, (wcslen(icongroupStr) + 1) * sizeof(WCHAR));
wsprintf(dateStr, L"%04d%02d%02d", stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay);
RegSetValueEx(rkey, L"InstallDate", 0, REG_SZ, (BYTE*)dateStr, (wcslen(dateStr) + 1) * sizeof(WCHAR));
WCHAR *appURL = L"https://desktop.telegram.org";
RegSetValueEx(rkey, L"HelpLink", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
RegSetValueEx(rkey, L"URLInfoAbout", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
RegSetValueEx(rkey, L"URLUpdateInfo", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
}
WCHAR *appURL = L"https://desktop.telegram.org";
RegSetValueEx(rkey, L"HelpLink", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
RegSetValueEx(rkey, L"URLInfoAbout", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
RegSetValueEx(rkey, L"URLUpdateInfo", 0, REG_SZ, (BYTE*)appURL, (wcslen(appURL) + 1) * sizeof(WCHAR));
}
}
}
}
RegCloseKey(rkey);
}
RegCloseKey(rkey);
}
} else {
writeLog(L"Could not open version file to update registry :(");
}
}
@@ -357,7 +378,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama
updateRegistry();
}
if (writeprotected) { // if we can't clear all tupdates\ready (Updater.exe is there) - clear only version
if (DeleteFile(L"tupdates\\ready\\tdata\\version")) {
if (DeleteFile(L"tupdates\\temp\\tdata\\version") || DeleteFile(L"tupdates\\ready\\tdata\\version")) {
writeLog(L"Version file deleted!");
} else {
writeLog(L"Error: could not delete version file");
@@ -386,7 +407,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama
HRESULT hres = CoInitialize(0);
if (SUCCEEDED(hres)) {
wstring lnk = L"tupdates\\ready\\temp.lnk";
IShellLink* psl;
HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
if (SUCCEEDED(hres)) {
@@ -401,7 +421,12 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdParama
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
if (SUCCEEDED(hres)) {
wstring lnk = L"tupdates\\temp\\temp.lnk";
hres = ppf->Save(lnk.c_str(), TRUE);
if (!SUCCEEDED(hres)) {
lnk = L"tupdates\\ready\\temp.lnk"; // old
hres = ppf->Save(lnk.c_str(), TRUE);
}
ppf->Release();
if (SUCCEEDED(hres)) {

View File

@@ -49,12 +49,13 @@ bool do_mkdir(const char *path) { // from http://stackoverflow.com/questions/675
}
bool _debug = false;
string exeName, exeDir, workDir;
FILE *_logFile = 0;
void openLog() {
if (!_debug || _logFile) return;
if (!do_mkdir("DebugLogs")) {
if (!do_mkdir((workDir + "DebugLogs").c_str())) {
return;
}
@@ -65,7 +66,7 @@ void openLog() {
static const int maxFileLen = 65536;
char logName[maxFileLen];
sprintf(logName, "DebugLogs/%04d%02d%02d_%02d%02d%02d_upd.txt",
sprintf(logName, "%sDebugLogs/%04d%02d%02d_%02d%02d%02d_upd.txt", workDir.c_str(),
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
_logFile = fopen(logName, "w");
}
@@ -136,7 +137,7 @@ bool remove_directory(const string &path) { // from http://stackoverflow.com/que
if (!d) {
writeLog("Could not open dir '%s'", path.c_str());
return false;
return (errno == ENOENT);
}
while (struct dirent *p = readdir(d)) {
@@ -192,8 +193,6 @@ bool mkpath(const char *path) {
return do_mkdir(path);
}
string exeName, exeDir, workDir;
bool equal(string a, string b) {
std::transform(a.begin(), a.end(), a.begin(), ::tolower);
std::transform(b.begin(), b.end(), b.begin(), ::tolower);
@@ -201,18 +200,33 @@ bool equal(string a, string b) {
}
void delFolder() {
string delPath = workDir + "tupdates/ready", delFolder = workDir + "tupdates";
writeLog("Fully clearing path '%s'..", delPath.c_str());
if (!remove_directory(delPath)) {
writeLog("Error: failed to clear path! :(");
string delPathOld = workDir + "tupdates/ready", delPath = workDir + "tupdates/temp", delFolder = workDir + "tupdates";
writeLog("Fully clearing old path '%s'..", delPathOld.c_str());
if (!remove_directory(delPathOld)) {
writeLog("Failed to clear old path! :( New path was used?..");
}
rmdir(delFolder.c_str());
writeLog("Fully clearing path '%s'..", delPath.c_str());
if (!remove_directory(delPath)) {
writeLog("Error: failed to clear path! :(");
}
rmdir(delFolder.c_str());
}
bool update() {
writeLog("Update started..");
string updDir = workDir + "tupdates/ready";
string updDir = workDir + "tupdates/temp", readyFilePath = workDir + "tupdates/temp/ready", tdataDir = workDir + "tupdates/temp/tdata";
{
FILE *readyFile = fopen(readyFilePath.c_str(), "rb");
if (readyFile) {
fclose(readyFile);
writeLog("Ready file found! Using new path '%s'..", updDir.c_str());
} else {
updDir = workDir + "tupdates/ready"; // old
tdataDir = workDir + "tupdates/ready/tdata";
writeLog("Ready file not found! Using old path '%s'..", updDir.c_str());
}
}
deque<string> dirs;
dirs.push_back(updDir);
@@ -242,7 +256,9 @@ bool update() {
string fname = dir + '/' + p->d_name;
struct stat statbuf;
if (!stat(fname.c_str(), &statbuf)) {
if (fname.substr(0, tdataDir.size()) == tdataDir && (fname.size() <= tdataDir.size() || fname.at(tdataDir.size()) == '/')) {
writeLog("Skipping 'tdata' path '%s'", fname.c_str());
} else if (!stat(fname.c_str(), &statbuf)) {
if (S_ISDIR(statbuf.st_mode)) {
dirs.push_back(fname);
writeLog("Added dir '%s' in update tree..", fname.c_str());
@@ -253,9 +269,13 @@ bool update() {
delFolder();
return false;
}
from.push_back(fname);
to.push_back(tofname);
writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str());
if (fname == readyFilePath) {
writeLog("Skipped ready file '%s'", fname.c_str());
} else {
from.push_back(fname);
to.push_back(tofname);
writeLog("Added file '%s' to be copied to '%s'", fname.c_str(), tofname.c_str());
}
}
} else {
writeLog("Could not get stat() for file %s", fname.c_str());
@@ -299,10 +319,6 @@ bool update() {
}
int main(int argc, char *argv[]) {
openLog();
writeLog("Updater started..");
bool needupdate = true, autostart = false, debug = false, tosettings = false, startintray = false, testmode = false;
char *key = 0;
@@ -313,7 +329,6 @@ int main(int argc, char *argv[]) {
autostart = true;
} else if (equal(argv[i], "-debug")) {
debug = _debug = true;
openLog();
} else if (equal(argv[i], "-startintray")) {
startintray = true;
} else if (equal(argv[i], "-testmode")) {
@@ -326,6 +341,12 @@ int main(int argc, char *argv[]) {
workDir = argv[i];
}
}
openLog();
writeLog("Updater started..");
for (int i = 0; i < argc; ++i) {
writeLog("Argument: '%s'", argv[i]);
}
if (needupdate) writeLog("Need to update!");
if (autostart) writeLog("From autostart!");
@@ -336,7 +357,7 @@ int main(int argc, char *argv[]) {
exeDir = exeName.substr(0, exeName.size() - 7);
writeLog("Exe dir is: %s", exeDir.c_str());
if (needupdate) {
if (workDir.empty()) { // old app launched
if (workDir.empty()) { // old app launched, update prepared in tupdates/ready (not in tupdates/temp)
writeLog("No workdir, trying to figure it out");
struct passwd *pw = getpwuid(getuid());
if (pw && pw->pw_dir && strlen(pw->pw_dir)) {

View File

@@ -35,7 +35,7 @@ void openLog() {
return;
}
NSDateFormatter *fmt = [[NSDateFormatter alloc] initWithDateFormat:@"DebugLogs/%Y%m%d %H%M%S_upd.txt" allowNaturalLanguage:NO];
NSDateFormatter *fmt = [[NSDateFormatter alloc] initWithDateFormat:@"DebugLogs/%Y%m%d_%H%M%S_upd.txt" allowNaturalLanguage:NO];
NSString *logPath = [workDir stringByAppendingString:[fmt stringFromDate:[NSDate date]]];
[[NSFileManager defaultManager] createFileAtPath:logPath contents:nil attributes:nil];
_logFile = [NSFileHandle fileHandleForWritingAtPath:logPath];
@@ -55,7 +55,14 @@ void writeLog(NSString *msg) {
}
void delFolder() {
[[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil];
writeLog([@"Fully clearing old path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/ready"]]);
if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/ready"] error:nil]) {
writeLog(@"Failed to clear old path! :( New path was used?..");
}
writeLog([@"Fully clearing new path: " stringByAppendingString:[workDir stringByAppendingString:@"tupdates/temp"]]);
if (![[NSFileManager defaultManager] removeItemAtPath:[workDir stringByAppendingString:@"tupdates/temp"] error:nil]) {
writeLog(@"Error: failed to clear new path! :(");
}
rmdir([[workDir stringByAppendingString:@"tupdates"] fileSystemRepresentation]);
}
@@ -132,13 +139,22 @@ int main(int argc, const char * argv[]) {
}
if (update) {
writeLog([@"Starting update files iteration, path: " stringByAppendingString: [workDir stringByAppendingString:@"tupdates/ready"]]);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *srcDir = [workDir stringByAppendingString:@"tupdates/ready/"];
NSString *readyFilePath = [workDir stringByAppendingString:@"tupdates/temp/ready"];
NSString *srcDir = [workDir stringByAppendingString:@"tupdates/temp/"], *srcEnum = [workDir stringByAppendingString:@"tupdates/temp"];
if ([fileManager fileExistsAtPath:readyFilePath]) {
writeLog([@"Ready file found! Using new path: " stringByAppendingString: srcEnum]);
} else {
srcDir = [workDir stringByAppendingString:@"tupdates/ready/"]; // old
srcEnum = [workDir stringByAppendingString:@"tupdates/ready"];
writeLog([@"Ready file not found! Using old path: " stringByAppendingString: srcEnum]);
}
writeLog([@"Starting update files iteration, path: " stringByAppendingString: srcEnum]);
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:[NSURL fileURLWithPath:[workDir stringByAppendingString:@"tupdates/ready"]]
enumeratorAtURL:[NSURL fileURLWithPath:srcEnum]
includingPropertiesForKeys:keys
options:0
errorHandler:^(NSURL *url, NSError *error) {
@@ -163,18 +179,20 @@ int main(int argc, const char * argv[]) {
NSString *dstPath = [appDirFull stringByAppendingString:[pathPart substringFromIndex:r.length]];
NSError *error;
NSNumber *isDirectory = nil;
writeLog([[NSArray arrayWithObjects: @"Copying file ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]);
if (![url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
writeLog([@"Failed to get IsDirectory for file " stringByAppendingString:[url path]]);
delFolder();
break;
}
if ([isDirectory boolValue]) {
writeLog([[NSArray arrayWithObjects: @"Copying dir ", srcPath, @" to ", dstPath, nil] componentsJoinedByString:@""]);
if (![fileManager createDirectoryAtPath:dstPath withIntermediateDirectories:YES attributes:nil error:nil]) {
writeLog([@"Failed to force path for directory " stringByAppendingString:dstPath]);
delFolder();
break;
}
} else if ([srcPath isEqualToString:readyFilePath]) {
writeLog([[NSArray arrayWithObjects: @"Skipping ready file ", srcPath, nil] componentsJoinedByString:@""]);
} else if ([fileManager fileExistsAtPath:dstPath]) {
if (![[NSData dataWithContentsOfFile:srcPath] writeToFile:dstPath atomically:YES]) {
writeLog([@"Failed to edit file " stringByAppendingString:dstPath]);

View File

@@ -119,6 +119,19 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result) {
App::feedUsers(d.vusers);
App::feedChats(d.vchats);
App::feedParticipants(f.vparticipants);
const QVector<MTPBotInfo> &v(f.vbot_info.c_vector().v);
for (QVector<MTPBotInfo>::const_iterator i = v.cbegin(), e = v.cend(); i < e; ++i) {
switch (i->type()) {
case mtpc_botInfo: {
const MTPDbotInfo &b(i->c_botInfo());
UserData *user = App::userLoaded(b.vuser_id.v);
if (user) {
user->setBotInfo(*i);
emit fullPeerUpdated(user);
}
} break;
}
}
PhotoData *photo = App::feedPhoto(f.vchat_photo);
ChatData *chat = peer->asChat();
if (chat) {
@@ -132,7 +145,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result) {
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), f.vnotify_settings);
_fullRequests.remove(peer);
emit fullPeerLoaded(peer);
emit fullPeerUpdated(peer);
}
void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result) {
@@ -142,8 +155,10 @@ void ApiWrap::gotUserFull(PeerData *peer, const MTPUserFull &result) {
App::feedUserLink(MTP_int(App::userFromPeer(peer->id)), d.vlink.c_contacts_link().vmy_link, d.vlink.c_contacts_link().vforeign_link);
App::main()->gotNotifySetting(MTP_inputNotifyPeer(peer->input), d.vnotify_settings);
peer->asUser()->setBotInfo(d.vbot_info);
_fullRequests.remove(peer);
emit fullPeerLoaded(peer);
emit fullPeerUpdated(peer);
}
bool ApiWrap::gotPeerFailed(PeerData *peer, const RPCError &error) {

View File

@@ -40,7 +40,7 @@ public:
signals:
void fullPeerLoaded(PeerData *peer);
void fullPeerUpdated(PeerData *peer);
public slots:

View File

@@ -55,6 +55,9 @@ namespace {
typedef QHash<WebPageId, WebPageData*> WebPagesData;
WebPagesData webPagesData;
typedef QMap<MsgId, ReplyMarkup> ReplyMarkups;
ReplyMarkups replyMarkups;
VideoItems videoItems;
AudioItems audioItems;
DocumentItems documentItems;
@@ -206,7 +209,11 @@ namespace App {
return (peer_id & 0x100000000L) ? int32(peer_id & 0xFFFFFFFFL) : 0;
}
int32 onlineForSort(int32 online, int32 now) {
int32 onlineForSort(UserData *user, int32 now) {
if (isServiceUser(user->id) || user->botInfo) {
return -1;
}
int32 online = user->onlineTill;
if (online <= 0) {
switch (online) {
case 0:
@@ -232,8 +239,12 @@ namespace App {
return online;
}
int32 onlineWillChangeIn(int32 online, int32 now) {
if (online <= 0) {
int32 onlineWillChangeIn(UserData *user, int32 now) {
if (isServiceUser(user->id) || user->botInfo) {
return 86400;
}
int32 online = user->onlineTill;
if (online <= 0) {
if (-online > now) return -online - now;
return 86400;
}
@@ -255,6 +266,8 @@ namespace App {
QString onlineText(UserData *user, int32 now, bool precise) {
if (isServiceUser(user->id)) {
return lang(lng_status_service_notifications);
} else if (user->botInfo) {
return lang(lng_status_bot);
}
int32 online = user->onlineTill;
if (online <= 0) {
@@ -333,88 +346,63 @@ namespace App {
data->setName(lang(lng_deleted), QString(), QString(), QString());
data->setPhoto(MTP_userProfilePhotoEmpty());
data->access = UserNoAccess;
data->setBotInfoVersion(-1);
wasContact = (data->contact > 0);
status = &emptyStatus;
data->contact = -1;
} break;
case mtpc_userDeleted: {
const MTPDuserDeleted &d(user.c_userDeleted());
case mtpc_user: {
const MTPDuser &d(user.c_user());
PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer);
data->input = MTP_inputPeerContact(d.vid);
data->inputUser = MTP_inputUserContact(d.vid);
data->setName(lang(lng_deleted), QString(), QString(), QString());
// data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
data->setPhoto(MTP_userProfilePhotoEmpty());
data->access = UserNoAccess;
int32 flags = d.vflags.v;
if (flags & MTPDuser_flag_self) {
data->input = MTP_inputPeerSelf();
data->inputUser = MTP_inputUserSelf();
} else if ((flags & (MTPDuser_flag_contact | MTPDuser_flag_mutual_contact)) || !d.has_access_hash()) {
data->input = MTP_inputPeerContact(d.vid);
data->inputUser = MTP_inputUserContact(d.vid);
} else {
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
}
if (flags & MTPDuser_flag_deleted) {
data->setPhone(QString());
data->setName(lang(lng_deleted), QString(), QString(), QString());
data->setPhoto(MTP_userProfilePhotoEmpty());
data->access = UserNoAccess;
status = &emptyStatus;
} else {
data->setPhone(d.has_phone() ? qs(d.vphone) : QString());
QString fname = d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString();
QString lname = d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString();
QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString();
bool showPhone = !isServiceUser(data->id) && !(flags & (MTPDuser_flag_self | MTPDuser_flag_contact | MTPDuser_flag_mutual_contact));
QString pname = (showPhone && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString();
data->setName(fname, lname, QString(), uname);
if (d.has_photo()) {
data->setPhoto(d.vphoto);
} else {
data->setPhoto(MTP_userProfilePhotoEmpty());
}
if (d.has_access_hash()) data->access = d.vaccess_hash.v;
status = d.has_status() ? &d.vstatus : &emptyStatus;
}
wasContact = (data->contact > 0);
status = &emptyStatus;
data->contact = -1;
} break;
case mtpc_userSelf: {
const MTPDuserSelf &d(user.c_userSelf());
PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer);
data->input = MTP_inputPeerSelf();
data->inputUser = MTP_inputUserSelf();
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
data->setPhoto(d.vphoto);
data->setPhone(qs(d.vphone));
data->access = 0;
wasContact = (data->contact > 0);
status = &d.vstatus;
if (::self != data) {
if (d.has_bot_info_version()) {
data->setBotInfoVersion(d.vbot_info_version.v);
data->botInfo->readsAllHistory = (d.vflags.v & MTPDuser_flag_bot_reads_all);
data->botInfo->cantJoinGroups = (d.vflags.v & MTPDuser_flag_bot_cant_join);
} else {
data->setBotInfoVersion(-1);
}
data->contact = (flags & (MTPDuser_flag_contact | MTPDuser_flag_mutual_contact)) ? 1 : (data->phone.isEmpty() ? -1 : 0);
if ((flags & MTPDuser_flag_self) && ::self != data) {
::self = data;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
} break;
case mtpc_userContact: {
const MTPDuserContact &d(user.c_userContact());
PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer);
data->input = MTP_inputPeerContact(d.vid);
data->inputUser = MTP_inputUserContact(d.vid);
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
data->setPhoto(d.vphoto);
data->setPhone(qs(d.vphone));
data->access = d.vaccess_hash.v;
wasContact = (data->contact > 0);
data->contact = 1;
status = &d.vstatus;
} break;
case mtpc_userRequest: {
const MTPDuserRequest &d(user.c_userRequest());
PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer);
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
data->setPhone(qs(d.vphone));
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), (!isServiceUser(data->id) && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString(), textOneLine(qs(d.vusername)));
data->setPhoto(d.vphoto);
data->access = d.vaccess_hash.v;
wasContact = (data->contact > 0);
data->contact = 0;
status = &d.vstatus;
} break;
case mtpc_userForeign: {
const MTPDuserForeign &d(user.c_userForeign());
PeerId peer(peerFromUser(d.vid.v));
data = App::user(peer);
data->input = MTP_inputPeerForeign(d.vid, d.vaccess_hash);
data->inputUser = MTP_inputUserForeign(d.vid, d.vaccess_hash);
data->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), QString(), textOneLine(qs(d.vusername)));
data->setPhoto(d.vphoto);
data->access = d.vaccess_hash.v;
wasContact = (data->contact > 0);
data->contact = -1;
status = &d.vstatus;
} break;
}
if (!data) continue;
@@ -472,6 +460,7 @@ namespace App {
if (data->version < d.vversion.v) {
data->version = d.vversion.v;
data->participants = ChatData::Participants();
data->botStatus = 0;
}
} break;
case mtpc_chatForbidden: {
@@ -505,6 +494,7 @@ namespace App {
if (data->version < d.vversion.v) {
data->version = d.vversion.v;
data->participants = ChatData::Participants();
data->botStatus = 0;
}/**/
} break;
}
@@ -518,7 +508,7 @@ namespace App {
return data;
}
void feedParticipants(const MTPChatParticipants &p) {
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos) {
switch (p.type()) {
case mtpc_chatParticipantsForbidden: {
const MTPDchatParticipantsForbidden &d(p.c_chatParticipantsForbidden());
@@ -545,17 +535,24 @@ namespace App {
}
} else {
chat->participants = ChatData::Participants();
chat->botStatus = 0;
break;
}
}
if (!chat->participants.isEmpty()) {
int32 botStatus = -1;
for (ChatData::Participants::iterator i = chat->participants.begin(), e = chat->participants.end(); i != e;) {
if (i.value() < pversion) {
i = chat->participants.erase(i);
} else {
if (i.key()->botInfo) {
botStatus = (botStatus > 0/* || i.key()->botInfo->readsAllHistory*/) ? 2 : 1;
if (requestBotInfos && !i.key()->botInfo->inited) App::api()->requestFullPeer(i.key());
}
++i;
}
}
chat->botStatus = botStatus;
}
if (App::main()) App::main()->peerUpdated(chat);
}
@@ -571,6 +568,7 @@ namespace App {
if (user) {
if (chat->participants.isEmpty() && chat->count) {
chat->count++;
chat->botStatus = 0;
} else if (chat->participants.find(user) == chat->participants.end()) {
chat->participants[user] = (chat->participants.isEmpty() ? 1 : chat->participants.begin().value());
if (d.vinviter_id.v == MTP::authedId()) {
@@ -579,9 +577,14 @@ namespace App {
chat->cankick.remove(user);
}
chat->count++;
if (user->botInfo) {
chat->botStatus = (chat->botStatus > 0/* || !user->botInfo->readsAllHistory*/) ? 2 : 1;
if (!user->botInfo->inited) App::api()->requestFullPeer(user);
}
}
} else {
chat->participants = ChatData::Participants();
chat->botStatus = 0;
chat->count++;
}
if (App::main()) App::main()->peerUpdated(chat);
@@ -602,9 +605,23 @@ namespace App {
chat->participants.erase(i);
chat->count--;
}
if (chat->botStatus > 0 && user->botInfo) {
int32 botStatus = -1;
for (ChatData::Participants::const_iterator j = chat->participants.cbegin(), e = chat->participants.cend(); j != e; ++j) {
if (j.key()->botInfo) {
if (botStatus > 0/* || !j.key()->botInfo->readsAllHistory*/) {
botStatus = 2;
break;
}
botStatus = 1;
}
}
chat->botStatus = botStatus;
}
}
} else {
chat->participants = ChatData::Participants();
chat->botStatus = 0;
chat->count--;
}
if (App::main()) App::main()->peerUpdated(chat);
@@ -731,11 +748,7 @@ namespace App {
MTPint userId(MTP_int(0));
switch (dv.vuser.type()) {
case mtpc_userEmpty: userId = dv.vuser.c_userEmpty().vid; break;
case mtpc_userDeleted: userId = dv.vuser.c_userDeleted().vid; break;
case mtpc_userContact: userId = dv.vuser.c_userContact().vid; break;
case mtpc_userSelf: userId = dv.vuser.c_userSelf().vid; break;
case mtpc_userRequest: userId = dv.vuser.c_userRequest().vid; break;
case mtpc_userForeign: userId = dv.vuser.c_userForeign().vid; break;
case mtpc_user: userId = dv.vuser.c_user().vid; break;
}
if (userId.v) {
feedUserLink(userId, dv.vmy_link, dv.vforeign_link);
@@ -896,6 +909,18 @@ namespace App {
return App::video(video.vid.v, convert, video.vaccess_hash.v, video.vuser_id.v, video.vdate.v, video.vduration.v, video.vw.v, video.vh.v, App::image(video.vthumb), video.vdc_id.v, video.vsize.v);
}
AudioData *feedAudio(const MTPaudio &audio, AudioData *convert) {
switch (audio.type()) {
case mtpc_audio: {
return feedAudio(audio.c_audio(), convert);
} break;
case mtpc_audioEmpty: {
return App::audio(audio.c_audioEmpty().vid.v, convert);
} break;
}
return App::audio(0);
}
AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert) {
return App::audio(audio.vid.v, convert, audio.vaccess_hash.v, audio.vuser_id.v, audio.vdate.v, qs(audio.vmime_type), audio.vduration.v, audio.vdc_id.v, audio.vsize.v);
}
@@ -1171,6 +1196,7 @@ namespace App {
}
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation) {
bool sentSticker = false;
if (convert) {
if (convert->id != document) {
DocumentsData::iterator i = documentsData.find(convert->id);
@@ -1179,6 +1205,7 @@ namespace App {
}
convert->id = document;
convert->status = FileReady;
sentSticker = !!convert->sticker;
}
convert->access = access;
if (!convert->date && date) {
@@ -1254,6 +1281,7 @@ namespace App {
}
}
}
if (sentSticker && App::main()) App::main()->incrementSticker(result);
return result;
}
@@ -1494,6 +1522,7 @@ namespace App {
}
::maxMsgId = 0;
::hoveredItem = ::pressedItem = ::hoveredLinkItem = ::pressedLinkItem = ::contextItem = 0;
replyMarkups.clear();
}
void historyClearItems() {
@@ -1649,6 +1678,9 @@ namespace App {
prepareCorners(MediaviewSaveCorners, st::msgRadius, st::emojiPanHover);
prepareCorners(EmojiHoverCorners, st::msgRadius, st::emojiPanHover);
prepareCorners(StickerHoverCorners, st::msgRadius, st::emojiPanHover);
prepareCorners(BotKeyboardCorners, st::msgRadius, st::botKbBg);
prepareCorners(BotKeyboardOverCorners, st::msgRadius, st::botKbOverBg);
prepareCorners(BotKeyboardDownCorners, st::msgRadius, st::botKbDownBg);
prepareCorners(MessageInCorners, st::msgRadius, st::msgInBg, &st::msgInShadow);
prepareCorners(MessageInSelectedCorners, st::msgRadius, st::msgInSelectBg, &st::msgInSelectShadow);
@@ -1943,6 +1975,56 @@ namespace App {
if (changeInMin) App::main()->updateMutedIn(changeInMin);
}
void feedReplyMarkup(MsgId msgId, const MTPReplyMarkup &markup) {
ReplyMarkup data;
ReplyMarkup::Commands &commands(data.commands);
switch (markup.type()) {
case mtpc_replyKeyboardMarkup: {
const MTPDreplyKeyboardMarkup &d(markup.c_replyKeyboardMarkup());
data.flags = d.vflags.v;
const QVector<MTPKeyboardButtonRow> &v(d.vrows.c_vector().v);
if (!v.isEmpty()) {
commands.reserve(v.size());
for (int32 i = 0, l = v.size(); i < l; ++i) {
switch (v.at(i).type()) {
case mtpc_keyboardButtonRow: {
const MTPDkeyboardButtonRow &r(v.at(i).c_keyboardButtonRow());
const QVector<MTPKeyboardButton> &b(r.vbuttons.c_vector().v);
if (!b.isEmpty()) {
QList<QString> btns;
btns.reserve(b.size());
for (int32 j = 0, s = b.size(); j < s; ++j) {
switch (b.at(j).type()) {
case mtpc_keyboardButton: {
btns.push_back(qs(b.at(j).c_keyboardButton().vtext));
} break;
}
}
if (!btns.isEmpty()) commands.push_back(btns);
}
} break;
}
}
if (!commands.isEmpty()) {
replyMarkups.insert(msgId, data);
}
}
} break;
}
}
void clearReplyMarkup(MsgId msgId) {
replyMarkups.remove(msgId);
}
const ReplyMarkup &replyMarkup(MsgId msgId) {
static ReplyMarkup zeroMarkup(MTPDreplyKeyboardMarkup_flag_ZERO);
ReplyMarkups::const_iterator i = replyMarkups.constFind(msgId);
if (i == replyMarkups.cend()) return zeroMarkup;
return i.value();
}
void setProxySettings(QNetworkAccessManager &manager) {
if (cConnectionType() == dbictHttpProxy) {
const ConnectionProxy &p(cConnectionProxy());
@@ -1961,15 +2043,27 @@ namespace App {
}
}
void sendBotCommand(const QString &cmd, MsgId replyTo) {
if (App::main()) {
App::main()->sendBotCommand(cmd, replyTo);
}
}
void insertBotCommand(const QString &cmd) {
if (App::main()) {
App::main()->insertBotCommand(cmd);
}
}
void searchByHashtag(const QString &tag) {
if (App::main()) {
App::main()->searchMessages(tag + ' ');
}
}
void openUserByName(const QString &username, bool toProfile) {
void openUserByName(const QString &username, bool toProfile, const QString &startToken) {
if (App::main()) {
App::main()->openUserByName(username, toProfile);
App::main()->openUserByName(username, toProfile, startToken);
}
}
@@ -2016,6 +2110,16 @@ namespace App {
p.drawPixmap(QPoint(x + w - cw, y + h - ch), *c[3]);
}
void roundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, const style::color &sh, RoundCorners index) {
QPixmap **c = App::corners(index);
int32 cw = c[0]->width() / cIntRetinaFactor(), ch = c[0]->height() / cIntRetinaFactor();
p.fillRect(x + cw, y + h, w - 2 * cw, st::msgShadow, sh->b);
p.fillRect(x, y + h - ch, cw, st::msgShadow, sh->b);
p.fillRect(x + w - cw, y + h - ch, cw, st::msgShadow, sh->b);
p.drawPixmap(x, y + h - ch + st::msgShadow, *c[2]);
p.drawPixmap(x + w - cw, y + h - ch + st::msgShadow, *c[3]);
}
void initBackground(int32 id, const QImage &p, bool nowrite) {
if (Local::readBackground()) return;

View File

@@ -35,6 +35,13 @@ typedef QHash<VideoData*, HistoryItemsMap> VideoItems;
typedef QHash<AudioData*, HistoryItemsMap> AudioItems;
typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
struct ReplyMarkup {
ReplyMarkup(int32 flags = 0) : flags(flags) {
}
typedef QList<QList<QString> > Commands;
Commands commands;
int32 flags;
};
enum RoundCorners {
MaskCorners = 0x00, // for images
@@ -48,6 +55,9 @@ enum RoundCorners {
MediaviewSaveCorners,
EmojiHoverCorners,
StickerHoverCorners,
BotKeyboardCorners,
BotKeyboardOverCorners,
BotKeyboardDownCorners,
InShadowCorners, // for photos without bg
InSelectedShadowCorners,
@@ -92,14 +102,14 @@ namespace App {
int32 userFromPeer(const PeerId &peer_id);
int32 chatFromPeer(const PeerId &peer_id);
int32 onlineForSort(int32 online, int32 now);
int32 onlineWillChangeIn(int32 onlineOnServer, int32 nowOnServer);
int32 onlineForSort(UserData *user, int32 now);
int32 onlineWillChangeIn(UserData *user, int32 nowOnServer);
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false);
bool onlineColorUse(int32 online, int32 now);
UserData *feedUsers(const MTPVector<MTPUser> &users); // returns last user
ChatData *feedChats(const MTPVector<MTPChat> &chats); // returns last chat
void feedParticipants(const MTPChatParticipants &p);
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos = false);
void feedParticipantAdd(const MTPDupdateChatParticipantAdd &d);
void feedParticipantDelete(const MTPDupdateChatParticipantDelete &d);
void feedMsgs(const MTPVector<MTPMessage> &msgs, int msgsState = 0); // 2 - new read message, 1 - new unread message, 0 - not new message, -1 - searched message
@@ -118,6 +128,7 @@ namespace App {
PhotoData *feedPhoto(const MTPPhoto &photo, PhotoData *convert = 0);
PhotoData *feedPhoto(const MTPDphoto &photo, PhotoData *convert = 0);
VideoData *feedVideo(const MTPDvideo &video, VideoData *convert = 0);
AudioData *feedAudio(const MTPaudio &audio, AudioData *convert = 0);
AudioData *feedAudio(const MTPDaudio &audio, AudioData *convert = 0);
DocumentData *feedDocument(const MTPdocument &document, const QPixmap &thumb);
DocumentData *feedDocument(const MTPdocument &document, DocumentData *convert = 0);
@@ -221,11 +232,17 @@ namespace App {
void unregMuted(PeerData *peer);
void updateMuted();
void feedReplyMarkup(MsgId msgId, const MTPReplyMarkup &markup);
void clearReplyMarkup(MsgId msgId);
const ReplyMarkup &replyMarkup(MsgId msgId);
void setProxySettings(QNetworkAccessManager &manager);
void setProxySettings(QTcpSocket &socket);
void sendBotCommand(const QString &cmd, MsgId replyTo = 0);
void insertBotCommand(const QString &cmd);
void searchByHashtag(const QString &tag);
void openUserByName(const QString &username, bool toProfile = false);
void openUserByName(const QString &username, bool toProfile = false, const QString &startToken = QString());
void joinGroupByHash(const QString &hash);
void stickersBox(const QString &name);
void openLocalUrl(const QString &url);
@@ -236,6 +253,10 @@ namespace App {
inline void roundRect(QPainter &p, const QRect &rect, const style::color &bg, RoundCorners index, const style::color *sh = 0) {
return roundRect(p, rect.x(), rect.y(), rect.width(), rect.height(), bg, index, sh);
}
void roundShadow(QPainter &p, int32 x, int32 y, int32 w, int32 h, const style::color &sh, RoundCorners index);
inline void roundShadow(QPainter &p, const QRect &rect, const style::color &sh, RoundCorners index) {
return roundShadow(p, rect.x(), rect.y(), rect.width(), rect.height(), sh, index);
}
void initBackground(int32 id = DefaultChatBackground, const QImage &p = QImage(), bool nowrite = false);

View File

@@ -29,6 +29,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "autoupdater.h"
namespace {
Application *mainApp = 0;
FileUploader *uploader = 0;
@@ -180,29 +182,6 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
}
}
void Application::onAppUpdate(const MTPhelp_AppUpdate &response) {
updateRequestId = 0;
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
if (response.type() == mtpc_help_noAppUpdate) {
startUpdateCheck();
} else {
updateThread = new QThread();
connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater()));
updateDownloader = new PsUpdateDownloader(updateThread, response.c_help_appUpdate());
updateThread->start();
}
}
bool Application::onAppUpdateFail() {
updateRequestId = 0;
cSetLastUpdateCheck(unixtime());
Local::writeSettings();
startUpdateCheck();
return true;
}
void Application::updateGotCurrent() {
if (!updateReply || updateThread) return;
@@ -213,7 +192,7 @@ void Application::updateGotCurrent() {
if (currentVersion > AppVersion) {
updateThread = new QThread();
connect(updateThread, SIGNAL(finished()), updateThread, SLOT(deleteLater()));
updateDownloader = new PsUpdateDownloader(updateThread, m.captured(2));
updateDownloader = new UpdateDownloader(updateThread, m.captured(2));
updateThread->start();
}
}
@@ -487,7 +466,7 @@ void Application::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId)
int32 filesize = 0;
QByteArray data;
ReadyLocalMedia ready(ToPreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, 0);
ReadyLocalMedia ready(ToPreparePhoto, file, filename, filesize, data, id, id, qsl("jpg"), peerId, photo, MTP_audioEmpty(MTP_long(0)), photoThumbs, MTP_documentEmpty(MTP_long(0)), jpeg, false, 0);
connect(App::uploader(), SIGNAL(photoReady(MsgId, const MTPInputFile &)), App::app(), SLOT(photoUpdated(MsgId, const MTPInputFile &)), Qt::UniqueConnection);
@@ -540,7 +519,6 @@ void Application::startUpdateCheck(bool forceWait) {
updateReply = updateManager.get(checkVersion);
connect(updateReply, SIGNAL(finished()), this, SLOT(updateGotCurrent()));
connect(updateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(updateFailedCurrent(QNetworkReply::NetworkError)));
// updateRequestId = MTP::send(MTPhelp_GetAppUpdate(MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(cApiLang())), rpcDone(&Application::onAppUpdate), rpcFail(&Application::onAppUpdateFail);
emit updateChecking();
} else {
updateCheckTimer.start((updateInSecs + 5) * 1000);
@@ -648,7 +626,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
return App::quit();
}
if (!cNoStartUpdate() && psCheckReadyUpdate()) {
if (!cNoStartUpdate() && checkReadyUpdate()) {
cSetRestartingUpdate(true);
DEBUG_LOG(("Application Info: installing update instead of starting app.."));
return App::quit();
@@ -658,16 +636,14 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
}
void Application::checkMapVersion() {
if (Local::oldMapVersion() < AppVersion) {
if (Local::oldMapVersion() < AppVersion) {
psRegisterCustomScheme();
if (Local::oldMapVersion()) {
QString versionFeatures;
if (DevChannel && Local::oldMapVersion() < 8015) {
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Video captions are displayed\n\xe2\x80\x94 Photo captions are displayed in photo viewer\n\xe2\x80\x94 Round corners for messages").replace('@', qsl("@") + QChar(0x200D));
} else if (!DevChannel && Local::oldMapVersion() < 8016) {
versionFeatures = lang(lng_new_version_text).trimmed();
} else if (!DevChannel && Local::oldMapVersion() < 8017) {
versionFeatures = lang(lng_new_version_minor).trimmed();
if (DevChannel && Local::oldMapVersion() < 8027) {
versionFeatures = lang(lng_new_version_minor);// QString::fromUtf8("\xe2\x80\x94 IPv6 connections support\n\xe2\x80\x94 Bug fixes and minor stuff");// .replace('@', qsl("@") + QChar(0x200D));
} else if (!DevChannel && Local::oldMapVersion() < 8024) {
versionFeatures = lng_new_version_text(lt_blog_link, qsl("https://telegram.org/blog/bot-revolution"));// lang(lng_new_version_text).trimmed();
}
if (!versionFeatures.isEmpty()) {
versionFeatures = lng_new_version_wrap(lt_version, QString::fromStdWString(AppVersionStr), lt_changes, versionFeatures, lt_link, qsl("https://desktop.telegram.org/#changelog"));

View File

@@ -27,6 +27,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
class MainWidget;
class FileUploader;
class Translator;
class UpdateDownloader;
class Application : public PsApplication, public RPCSender {
Q_OBJECT
@@ -42,9 +43,6 @@ public:
static int32 languageId();
static MainWidget *main();
void onAppUpdate(const MTPhelp_AppUpdate &response);
bool onAppUpdateFail();
enum UpdatingState {
UpdatingNone,
UpdatingDownload,
@@ -80,6 +78,12 @@ public:
signals:
void updateChecking();
void updateLatest();
void updateDownloading(qint64 ready, qint64 total);
void updateReady();
void updateFailed();
void peerPhotoDone(PeerId peer);
void peerPhotoFail(PeerId peer);
@@ -143,7 +147,7 @@ private:
QNetworkReply *updateReply;
SingleTimer updateCheckTimer;
QThread *updateThread;
PsUpdateDownloader *updateDownloader;
UpdateDownloader *updateDownloader;
QTimer writeUserConfigTimer;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 215 KiB

After

Width:  |  Height:  |  Size: 219 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -24,33 +24,35 @@ bool audioWorks();
void audioPlayNotify();
void audioFinish();
enum VoiceMessageState {
VoiceMessageStopped,
VoiceMessageStarting,
VoiceMessagePlaying,
VoiceMessageFinishing,
VoiceMessagePausing,
VoiceMessagePaused,
VoiceMessageResuming,
enum AudioPlayerState {
AudioPlayerStopped,
AudioPlayerStoppedAtStart,
AudioPlayerStarting,
AudioPlayerPlaying,
AudioPlayerFinishing,
AudioPlayerPausing,
AudioPlayerPaused,
AudioPlayerResuming,
};
class VoiceMessagesFader;
class VoiceMessagesLoader;
class AudioPlayerFader;
class AudioPlayerLoaders;
class VoiceMessages : public QObject {
class AudioPlayer : public QObject {
Q_OBJECT
public:
VoiceMessages();
AudioPlayer();
void play(AudioData *audio);
void pauseresume();
void currentState(AudioData **audio, VoiceMessageState *state = 0, int64 *position = 0, int64 *duration = 0);
void processContext();
void currentState(AudioData **audio, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0);
void clearStoppedAtStart(AudioData *audio);
void resumeDevice();
~VoiceMessages();
~AudioPlayer();
public slots:
@@ -70,8 +72,8 @@ private:
bool updateCurrentStarted(int32 pos = -1);
struct Msg {
Msg() : audio(0), position(0), duration(0), skipStart(0), skipEnd(0), loading(0), started(0),
state(VoiceMessageStopped), source(0), nextBuffer(0) {
Msg() : audio(0), position(0), duration(0), frequency(AudioVoiceMsgFrequency), skipStart(0), skipEnd(0), loading(0), started(0),
state(AudioPlayerStopped), source(0), nextBuffer(0) {
memset(buffers, 0, sizeof(buffers));
memset(samplesCount, 0, sizeof(samplesCount));
}
@@ -79,10 +81,11 @@ private:
QString fname;
QByteArray data;
int64 position, duration;
int32 frequency;
int64 skipStart, skipEnd;
bool loading;
int64 started;
VoiceMessageState state;
AudioPlayerState state;
uint32 source;
int32 nextBuffer;
@@ -95,25 +98,59 @@ private:
QMutex _mutex;
friend class VoiceMessagesFader;
friend class VoiceMessagesLoader;
friend class AudioPlayerFader;
friend class AudioPlayerLoaders;
QThread _faderThread;
QThread _loaderThread;
VoiceMessagesFader *_fader;
VoiceMessagesLoader *_loader;
QThread _faderThread, _loaderThread;
AudioPlayerFader *_fader;
AudioPlayerLoaders *_loader;
};
VoiceMessages *audioVoice();
class AudioCaptureInner;
class VoiceMessagesFader : public QObject {
class AudioCapture : public QObject {
Q_OBJECT
public:
VoiceMessagesFader(QThread *thread);
void processContext();
AudioCapture();
void start();
void stop(bool needResult);
bool check();
~AudioCapture();
signals:
void captureOnStart();
void captureOnStop(bool needResult);
void onDone(QByteArray data, qint32 samples);
void onUpdate(qint16 level, qint32 samples);
void onError();
private:
friend class AudioCaptureInner;
QThread _captureThread;
AudioCaptureInner *_capture;
};
AudioPlayer *audioPlayer();
AudioCapture *audioCapture();
class AudioPlayerFader : public QObject {
Q_OBJECT
public:
AudioPlayerFader(QThread *thread);
void resumeDevice();
signals:
@@ -122,30 +159,31 @@ signals:
void audioStopped(AudioData *audio);
void needToPreload(AudioData *audio);
void stopSuspend();
void stopPauseDevice();
public slots:
void onInit();
void onTimer();
void onSuspendTimer();
void onSuspendTimerStop();
void onPauseTimer();
void onPauseTimerStop();
private:
QTimer _timer, _suspendTimer;
QMutex _suspendMutex;
bool _suspendFlag;
QTimer _timer, _pauseTimer;
QMutex _pauseMutex;
bool _pauseFlag, _paused;
};
class VoiceMessagesLoader : public QObject {
class AudioPlayerLoader;
class AudioPlayerLoaders : public QObject {
Q_OBJECT
public:
VoiceMessagesLoader(QThread *thread);
~VoiceMessagesLoader();
AudioPlayerLoaders(QThread *thread);
~AudioPlayerLoaders();
signals:
@@ -161,10 +199,43 @@ public slots:
private:
struct Loader;
typedef QMap<AudioData*, Loader*> Loaders;
typedef QMap<AudioData*, AudioPlayerLoader*> Loaders;
Loaders _loaders;
void loadError(Loaders::iterator i);
};
struct AudioCapturePrivate;
class AudioCaptureInner : public QObject {
Q_OBJECT
public:
AudioCaptureInner(QThread *thread);
~AudioCaptureInner();
signals:
void error();
void update(qint16 level, qint32 samples);
void done(QByteArray data, qint32 samples);
public slots:
void onInit();
void onStart();
void onStop(bool needResult);
void onTimeout();
private:
void writeFrame(int32 offset, int32 framesize);
AudioCapturePrivate *d;
QTimer _timer;
QByteArray _captured;
};

View File

@@ -0,0 +1,523 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "application.h"
#include "pspecific.h"
#include "autoupdater.h"
#ifdef Q_OS_WIN
typedef DWORD VerInt;
typedef WCHAR VerChar;
#else
typedef int VerInt;
typedef wchar_t VerChar;
#endif
UpdateDownloader::UpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
updateUrl = url;
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
void UpdateDownloader::initOutput() {
QString fileName;
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
if (m.hasMatch()) {
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand());
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
QFileInfo file(fileName);
QDir dir(dirStr);
if (dir.exists()) {
QFileInfoList all = dir.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
if (i->absoluteFilePath() != file.absoluteFilePath()) {
QFile::remove(i->absoluteFilePath());
}
}
} else {
dir.mkdir(dir.absolutePath());
}
outputFile.setFileName(fileName);
if (file.exists()) {
uint64 fullSize = file.size();
if (fullSize < INT_MAX) {
int32 goodSize = (int32)fullSize;
if (goodSize % UpdateChunk) {
goodSize = goodSize - (goodSize % UpdateChunk);
if (goodSize) {
if (outputFile.open(QIODevice::ReadOnly)) {
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
outputFile.close();
if (outputFile.open(QIODevice::WriteOnly)) {
outputFile.write(goodData);
outputFile.close();
QMutexLocker lock(&mutex);
already = goodSize;
}
}
}
} else {
QMutexLocker lock(&mutex);
already = goodSize;
}
}
if (!already) {
QFile::remove(fileName);
}
}
}
void UpdateDownloader::start() {
sendRequest();
}
void UpdateDownloader::sendRequest() {
QNetworkRequest req(updateUrl);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";
req.setRawHeader("Range", rangeHeaderValue);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
if (reply) reply->deleteLater();
reply = manager.get(req);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
}
void UpdateDownloader::partMetaGot() {
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
Pairs pairs = reply->rawHeaderPairs();
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
if (QString::fromUtf8(i->first).toLower() == "content-range") {
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
if (m.hasMatch()) {
{
QMutexLocker lock(&mutex);
full = m.captured(1).toInt();
}
emit App::app()->updateDownloading(already, full);
}
}
}
}
int32 UpdateDownloader::ready() {
QMutexLocker lock(&mutex);
return already;
}
int32 UpdateDownloader::size() {
QMutexLocker lock(&mutex);
return full;
}
void UpdateDownloader::partFinished(qint64 got, qint64 total) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status != 200 && status != 206 && status != 416) {
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
return fatalFail();
}
}
if (!already && !full) {
QMutexLocker lock(&mutex);
full = total;
}
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
if (!outputFile.isOpen()) {
if (!outputFile.open(QIODevice::Append)) {
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
return fatalFail();
}
}
QByteArray r = reply->readAll();
if (!r.isEmpty()) {
outputFile.write(r);
QMutexLocker lock(&mutex);
already += r.size();
}
if (got >= total) {
reply->deleteLater();
reply = 0;
outputFile.close();
unpackUpdate();
} else {
emit App::app()->updateDownloading(already, full);
}
}
void UpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
reply = 0;
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status == 416) { // Requested range not satisfiable
outputFile.close();
unpackUpdate();
return;
}
}
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
emit App::app()->updateFailed();
}
void UpdateDownloader::fatalFail() {
clearAll();
emit App::app()->updateFailed();
}
void UpdateDownloader::clearAll() {
psDeleteDir(cWorkingDir() + qsl("tupdates"));
}
//QString winapiErrorWrap() {
// WCHAR errMsg[2048];
// DWORD errorCode = GetLastError();
// LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)";
// FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0);
// if (!errorText) {
// errorText = errorTextDefault;
// }
// StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText);
// if (errorText != errorTextDefault) {
// LocalFree(errorText);
// }
// return QString::fromWCharArray(errMsg);
//}
void UpdateDownloader::unpackUpdate() {
QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
return fatalFail();
}
#ifdef Q_OS_WIN // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
#else
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
#endif
QByteArray compressed = outputFile.readAll();
int32 compressedLen = compressed.size() - hSize;
if (compressedLen <= 0) {
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
return fatalFail();
}
outputFile.close();
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready");
psDeleteDir(tempDirPath);
QDir tempDir(tempDirPath);
if (tempDir.exists() || QFile(readyFilePath).exists()) {
LOG(("Update Error: cant clear tupdates/temp dir!"));
return fatalFail();
}
uchar sha1Buffer[20];
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
if (!goodSha1) {
LOG(("Update Error: bad SHA1 hash of update file!"));
return fatalFail();
}
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
if (!pbKey) {
LOG(("Update Error: cant read public rsa key!"));
return fatalFail();
}
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
RSA_free(pbKey);
LOG(("Update Error: bad RSA signature of update file!"));
return fatalFail();
}
RSA_free(pbKey);
QByteArray uncompressed;
int32 uncompressedLen;
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
uncompressed.resize(uncompressedLen);
size_t resultLen = uncompressed.size();
#ifdef Q_OS_WIN // use Lzma SDK for win
SizeT srcLen = compressedLen;
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
if (uncompressRes != SZ_OK) {
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
return fatalFail();
}
#else
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
if (ret != LZMA_OK) {
const char *msg;
switch (ret) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
return fatalFail();
}
stream.avail_in = compressedLen;
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
stream.avail_out = resultLen;
stream.next_out = (uint8_t*)uncompressed.data();
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
if (stream.avail_in) {
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
return fatalFail();
} else if (stream.avail_out) {
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
return fatalFail();
}
lzma_end(&stream);
if (res != LZMA_OK && res != LZMA_STREAM_END) {
const char *msg;
switch (res) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
return fatalFail();
}
#endif
tempDir.mkdir(tempDir.absolutePath());
quint32 version;
{
QBuffer buffer(&uncompressed);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
stream >> version;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (int32(version) <= AppVersion) {
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
return fatalFail();
}
quint32 filesCount;
stream >> filesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (!filesCount) {
LOG(("Update Error: update is empty!"));
return fatalFail();
}
for (uint32 i = 0; i < filesCount; ++i) {
QString relativeName;
quint32 fileSize;
QByteArray fileInnerData;
bool executable = false;
stream >> relativeName >> fileSize >> fileInnerData;
#if defined Q_OS_MAC || defined Q_OS_LINUX
stream >> executable;
#endif
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (fileSize != quint32(fileInnerData.size())) {
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
return fatalFail();
}
QFile f(tempDirPath + '/' + relativeName);
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (f.write(fileInnerData) != fileSize) {
f.close();
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
f.close();
if (executable) {
QFileDevice::Permissions p = f.permissions();
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
f.setPermissions(p);
}
}
// create tdata/version file
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
VerChar versionStr[32];
memcpy(versionStr, versionString.c_str(), versionLen);
QFile fVersion(tempDirPath + qsl("/tdata/version"));
if (!fVersion.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
return fatalFail();
}
fVersion.write((const char*)&versionNum, sizeof(VerInt));
fVersion.write((const char*)&versionLen, sizeof(VerInt));
fVersion.write((const char*)&versionStr[0], versionLen);
fVersion.close();
}
QFile readyFile(readyFilePath);
if (readyFile.open(QIODevice::WriteOnly)) {
if (readyFile.write("1", 1)) {
readyFile.close();
} else {
LOG(("Update Error: cant write ready file '%1'").arg(readyFilePath));
return fatalFail();
}
} else {
LOG(("Update Error: cant create ready file '%1'").arg(readyFilePath));
return fatalFail();
}
outputFile.remove();
emit App::app()->updateReady();
}
UpdateDownloader::~UpdateDownloader() {
delete reply;
reply = 0;
}
bool checkReadyUpdate() {
QString readyFilePath = cWorkingDir() + qsl("tupdates/temp/ready"), readyPath = cWorkingDir() + qsl("tupdates/temp");
if (!QFile(readyFilePath).exists()) {
if (QDir(cWorkingDir() + qsl("tupdates/ready")).exists() || QDir(cWorkingDir() + qsl("tupdates/temp")).exists()) {
UpdateDownloader::clearAll();
}
return false;
}
// check ready version
QString versionPath = readyPath + qsl("/tdata/version");
{
QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
UpdateDownloader::clearAll();
return false;
}
VerInt versionNum;
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
UpdateDownloader::clearAll();
return false;
}
fVersion.close();
if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
UpdateDownloader::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater.exe"));
#elif defined Q_OS_MAC
QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Telegram.app/Contents/Frameworks/Updater"));
#elif defined Q_OS_LINUX
QString curUpdater = (cExeDir() + qsl("Updater"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/temp/Updater"));
#endif
if (!updater.exists()) {
QFileInfo current(curUpdater);
if (!current.exists()) {
UpdateDownloader::clearAll();
return false;
}
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
UpdateDownloader::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
DWORD errorCode = GetLastError();
if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files
cSetWriteProtected(true);
return true;
} else {
UpdateDownloader::clearAll();
return false;
}
}
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
UpdateDownloader::clearAll();
return false;
}
#elif defined Q_OS_MAC
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater));
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
UpdateDownloader::clearAll();
return false;
}
#elif defined Q_OS_LINUX
if (!linuxMoveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
UpdateDownloader::clearAll();
return false;
}
#endif
return true;
}

View File

@@ -0,0 +1,62 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It 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.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014 John Preston, https://desktop.telegram.org
*/
#pragma once
#include <QtNetwork/QLocalSocket>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QNetworkReply>
class UpdateDownloader : public QObject {
Q_OBJECT
public:
UpdateDownloader(QThread *thread, const QString &url);
void unpackUpdate();
int32 ready();
int32 size();
static void clearAll();
~UpdateDownloader();
public slots:
void start();
void partMetaGot();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void sendRequest();
private:
void initOutput();
void fatalFail();
QString updateUrl;
QNetworkAccessManager manager;
QNetworkReply *reply;
int32 already, full;
QFile outputFile;
QMutex mutex;
};
bool checkReadyUpdate();

View File

@@ -23,7 +23,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "window.h"
ContactsInner::ContactsInner(bool creatingChat) : _chat(0), _creatingChat(creatingChat),
#include "confirmbox.h"
ContactsInner::ContactsInner(bool creatingChat) : _chat(0), _bot(0), _creatingChat(creatingChat), _addToChat(0),
_contacts(&App::main()->contactsList()),
_sel(0),
_filteredSel(-1),
@@ -35,7 +37,7 @@ _addContactLnk(this, lang(lng_add_contact_button)) {
init();
}
ContactsInner::ContactsInner(ChatData *chat) : _chat(chat), _creatingChat(false),
ContactsInner::ContactsInner(ChatData *chat) : _chat(chat), _bot(0), _creatingChat(false), _addToChat(0),
_contacts(&App::main()->contactsList()),
_sel(0),
_filteredSel(-1),
@@ -47,6 +49,24 @@ _addContactLnk(this, lang(lng_add_contact_button)) {
init();
}
ContactsInner::ContactsInner(UserData *bot) : _chat(0), _bot(bot), _creatingChat(false), _addToChat(0),
_contacts(new DialogsIndexed(DialogsSortByAdd)),
_sel(0),
_filteredSel(-1),
_mouseSel(false),
_selCount(0),
_searching(false),
_byUsernameSel(-1),
_addContactLnk(this, lang(lng_add_contact_button)) {
DialogsIndexed &v(App::main()->dialogsList());
for (DialogRow *r = v.list.begin; r != v.list.end; r = r->next) {
if (r->history->peer->chat && !r->history->peer->asChat()->forbidden) {
_contacts->addToEnd(r->history);
}
}
init();
}
void ContactsInner::init() {
connect(&_addContactLnk, SIGNAL(clicked()), App::wnd(), SLOT(onShowAddContact()));
@@ -59,10 +79,29 @@ void ContactsInner::init() {
connect(App::main(), SIGNAL(dialogRowReplaced(DialogRow *, DialogRow *)), this, SLOT(onDialogRowReplaced(DialogRow *, DialogRow *)));
connect(App::main(), SIGNAL(peerUpdated(PeerData*)), this, SLOT(peerUpdated(PeerData *)));
connect(App::main(), SIGNAL(peerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)), this, SLOT(peerUpdated(PeerData *)));
connect(App::main(), SIGNAL(peerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)), this, SLOT(onPeerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)));
connect(App::main(), SIGNAL(peerPhotoChanged(PeerData *)), this, SLOT(peerUpdated(PeerData *)));
}
void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
if (bot()) {
_contacts->peerNameChanged(peer, oldNames, oldChars);
}
peerUpdated(peer);
}
void ContactsInner::onAddBot() {
if (_bot->botInfo && !_bot->botInfo->startGroupToken.isEmpty()) {
uint64 randomId = MTP::nonce<uint64>();
MTP::send(MTPmessages_StartBot(_bot->inputUser, MTP_int(App::chatFromPeer(_addToChat->id)), MTP_long(randomId), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
App::wnd()->hideLayer();
App::main()->showPeer(_addToChat->id, 0, false);
} else {
App::main()->addParticipants(_addToChat, QVector<UserData*>(1, _bot));
}
}
void ContactsInner::peerUpdated(PeerData *peer) {
if (_chat && (!peer || peer == _chat)) {
if (_chat->forbidden) {
@@ -81,8 +120,8 @@ void ContactsInner::peerUpdated(PeerData *peer) {
}
}
}
} else if (!peer->chat) {
ContactsData::iterator i = _contactsData.find(peer->asUser());
} else {
ContactsData::iterator i = _contactsData.find(peer);
if (i != _contactsData.cend()) {
for (DialogRow *row = _contacts->list.begin; row->next; row = row->next) {
if (row->attached == i.value()) row->attached = 0;
@@ -136,14 +175,23 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) {
ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
ContactData *data = (ContactData*)row->attached;
if (!data) {
UserData *user = row->history->peer->asUser();
ContactsData::const_iterator i = _contactsData.constFind(user);
PeerData *peer = row->history->peer;
ContactsData::const_iterator i = _contactsData.constFind(peer);
if (i == _contactsData.cend()) {
_contactsData.insert(user, data = new ContactData());
data->inchat = _chat ? _chat->participants.contains(user) : false;
_contactsData.insert(peer, data = new ContactData());
data->inchat = (_chat && !peer->chat) ? _chat->participants.contains(peer->asUser()) : false;
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user, _time);
data->name.setText(st::profileListNameFont, peer->name, _textNameOptions);
if (peer->chat) {
ChatData *chat = peer->asChat();
if (chat->forbidden) {
data->online = lang(lng_chat_status_unaccessible);
} else {
data->online = lng_chat_status_members(lt_count, chat->count);
}
} else {
data->online = App::onlineText(peer->asUser(), _time);
}
} else {
data = i.value();
}
@@ -152,9 +200,11 @@ ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
return data;
}
void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel) {
void ContactsInner::paintDialog(QPainter &p, PeerData *peer, ContactData *data, bool sel) {
int32 left = st::profileListPadding.width();
UserData *user = peer->chat ? 0 : peer->asUser();
if (data->inchat || data->check || _selCount + (_chat ? _chat->count : 0) >= cMaxGroupCount()) {
sel = false;
}
@@ -163,7 +213,7 @@ void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data,
p.fillRect(0, 0, width(), 2 * st::profileListPadding.height() + st::profileListPhotoSize, ((data->inchat || data->check) ? st::profileActiveBG : st::profileHoverBG)->b);
}
p.drawPixmap(left, st::profileListPadding.height(), user->photo->pix(st::profileListPhotoSize));
p.drawPixmap(left, st::profileListPadding.height(), peer->photo->pix(st::profileListPhotoSize));
if (data->inchat || data->check) {
p.setPen(st::white->p);
@@ -181,8 +231,7 @@ void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data,
p.drawPixmap(QPoint(width() - st::contactsImg.pxWidth() - st::profileCheckDeltaX, st::profileListPadding.height() + (st::profileListPhotoSize - st::contactsImg.pxHeight()) / 2 - st::profileCheckDeltaY), App::sprite(), st::contactsImg);
}
bool uname = (data->online.at(0) == '@');
bool uname = user && (data->online.at(0) == '@');
p.setFont(st::profileSubFont->f);
if (uname && !data->inchat && !data->check && !_lastQuery.isEmpty() && user->username.startsWith(_lastQuery, Qt::CaseInsensitive)) {
int32 availw = width() - (left + st::profileListPhotoSize + st::profileListPadding.width() * 2);
@@ -200,8 +249,10 @@ void ContactsInner::paintDialog(QPainter &p, UserData *user, ContactData *data,
} else {
if (data->inchat || data->check) {
p.setPen(st::white->p);
} else if (user && (uname || App::onlineColorUse(user->onlineTill, _time))) {
p.setPen(st::profileOnlineColor->p);
} else {
p.setPen(((uname || App::onlineColorUse(user->onlineTill, _time)) ? st::profileOnlineColor : st::profileOfflineColor)->p);
p.setPen(st::profileOfflineColor->p);
}
p.drawText(left + st::profileListPhotoSize + st::profileListPadding.width(), st::profileListPadding.height() + st::profileListPhotoSize - st::profileListStatusBottom, data->online);
}
@@ -224,7 +275,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
DialogRow *drawFrom = _contacts->list.current;
p.translate(0, drawFrom->pos * rh);
while (drawFrom != _contacts->list.end && drawFrom->pos * rh < yTo) {
paintDialog(p, drawFrom->history->peer->asUser(), contactData(drawFrom), (drawFrom == _sel));
paintDialog(p, drawFrom->history->peer, contactData(drawFrom), (drawFrom == _sel));
p.translate(0, rh);
drawFrom = drawFrom->next;
}
@@ -253,13 +304,29 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
} else {
p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight - ((cContactsReceived() && !_searching) ? st::noContactsFont->height : 0)), lang((cContactsReceived() && !_searching) ? lng_no_contacts : lng_contacts_loading), style::al_center);
QString text;
int32 skip = 0;
if (bot()) {
text = lang(cDialogsReceived() ? lng_bot_no_groups : lng_contacts_loading);
} else if (cContactsReceived() && !_searching) {
text = lang(lng_no_contacts);
skip = st::noContactsFont->height;
} else {
text = lang(lng_contacts_loading);
}
p.drawText(QRect(0, 0, width(), st::noContactsHeight - skip), text, style::al_center);
}
} else {
if (_filtered.isEmpty() && _byUsernameFiltered.isEmpty()) {
p.setFont(st::noContactsFont->f);
p.setPen(st::noContactsColor->p);
p.drawText(QRect(0, 0, width(), st::noContactsHeight), lang((cContactsReceived() && !_searching) ? lng_contacts_not_found : lng_contacts_loading), style::al_center);
QString text;
if (bot()) {
text = lang(cDialogsReceived() ? lng_bot_groups_not_found : lng_contacts_loading);
} else {
text = lang((cContactsReceived() && !_searching) ? lng_contacts_not_found : lng_contacts_loading);
}
p.drawText(QRect(0, 0, width(), st::noContactsHeight), text, style::al_center);
} else {
if (!_filtered.isEmpty()) {
int32 from = (yFrom >= 0) ? (yFrom / rh) : 0;
@@ -269,7 +336,7 @@ void ContactsInner::paintEvent(QPaintEvent *e) {
p.translate(0, from * rh);
for (; from < to; ++from) {
paintDialog(p, _filtered[from]->history->peer->asUser(), contactData(_filtered[from]), (_filteredSel == from));
paintDialog(p, _filtered[from]->history->peer, contactData(_filtered[from]), (_filteredSel == from));
p.translate(0, rh);
}
}
@@ -371,25 +438,32 @@ void ContactsInner::chooseParticipant() {
}
} else {
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from;
PeerId peer = 0;
PeerData *peer = 0;
if (_filter.isEmpty()) {
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsername.size()) {
peer = _byUsername[_byUsernameSel]->id;
} else {
peer = _sel ? _sel->history->peer->id : 0;
peer = _byUsername[_byUsernameSel];
} else if (_sel) {
peer = _sel->history->peer;
}
} else {
if (_byUsernameSel >= 0 && _byUsernameSel < _byUsernameFiltered.size()) {
peer = _byUsernameFiltered[_byUsernameSel]->id;
peer = _byUsernameFiltered[_byUsernameSel];
} else {
if (_filteredSel < 0 || _filteredSel >= _filtered.size()) return;
peer = _filtered[_filteredSel]->history->peer->id;
peer = _filtered[_filteredSel]->history->peer;
}
}
if (peer) {
App::wnd()->hideSettings(true);
App::main()->showPeer(peer, 0, false, true);
App::wnd()->hideLayer();
if (bot() && peer->chat) {
_addToChat = peer->asChat();
ConfirmBox *box = new ConfirmBox(lng_bot_sure_invite(lt_group, peer->name));
connect(box, SIGNAL(confirmed()), this, SLOT(onAddBot()));
App::wnd()->replaceLayer(box);
} else {
App::wnd()->hideSettings(true);
App::main()->showPeer(peer->id, 0, false, true);
App::wnd()->hideLayer();
}
}
}
parentWidget()->update();
@@ -562,8 +636,10 @@ void ContactsInner::updateFilter(QString filter) {
refresh();
_searching = true;
emit searchByUsername();
if (!bot()) {
_searching = true;
emit searchByUsername();
}
}
if (parentWidget()) parentWidget()->update();
loadProfilePhotos(0);
@@ -612,6 +688,8 @@ void ContactsInner::peopleReceived(const QString &query, const QVector<MTPContac
}
if (j == already) {
UserData *u = App::user(uid);
if (u->botInfo && u->botInfo->cantJoinGroups && (_chat || _creatingChat)) continue; // skip bot's that can't be invited to groups
ContactData *d = new ContactData();
_byUsernameDatas.push_back(d);
d->inchat = _chat ? _chat->participants.contains(u) : false;
@@ -634,7 +712,7 @@ void ContactsInner::refresh() {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
resize(width(), (_contacts->list.count * rh) + (_byUsername.isEmpty() ? 0 : (st::searchedBarHeight + _byUsername.size() * rh)));
} else {
if (cContactsReceived()) {
if (cContactsReceived() && !bot()) {
if (_addContactLnk.isHidden()) _addContactLnk.show();
} else {
if (!_addContactLnk.isHidden()) _addContactLnk.hide();
@@ -656,6 +734,10 @@ ChatData *ContactsInner::chat() const {
return _chat;
}
UserData *ContactsInner::bot() const {
return _bot;
}
bool ContactsInner::creatingChat() const {
return _creatingChat;
}
@@ -664,6 +746,10 @@ ContactsInner::~ContactsInner() {
for (ContactsData::iterator i = _contactsData.begin(), e = _contactsData.end(); i != e; ++i) {
delete *i;
}
if (_bot) {
delete _contacts;
if (_bot->botInfo) _bot->botInfo->startGroupToken = QString();
}
}
void ContactsInner::resizeEvent(QResizeEvent *e) {
@@ -796,8 +882,8 @@ QVector<UserData*> ContactsInner::selected() {
QVector<UserData*> result;
result.reserve(_contactsData.size());
for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
if (i.value()->check) {
result.push_back(i.key());
if (i.value()->check && !i.key()->chat) {
result.push_back(i.key()->asUser());
}
}
for (int32 i = 0, l = _byUsername.size(); i < l; ++i) {
@@ -812,7 +898,7 @@ QVector<MTPInputUser> ContactsInner::selectedInputs() {
QVector<MTPInputUser> result;
result.reserve(_contactsData.size());
for (ContactsData::const_iterator i = _contactsData.cbegin(), e = _contactsData.cend(); i != e; ++i) {
if (i.value()->check) {
if (i.value()->check && !i.key()->chat) {
result.push_back(i.key()->inputUser);
}
}
@@ -854,6 +940,14 @@ _cancel(this, lang(lng_cancel), st::btnSelectCancel) {
init();
}
ContactsBox::ContactsBox(UserData *bot) : ItemListBox(st::boxNoTopScroll), _inner(bot),
_addContact(this, lang(lng_add_contact_button), st::contactsAdd),
_filter(this, st::contactsFilter, lang(lng_participant_filter)),
_next(this, lang(lng_create_group_next), st::btnSelectDone),
_cancel(this, lang(lng_cancel), st::contactsClose) {
init();
}
void ContactsBox::init() {
ItemListBox::init(&_inner, _cancel.height(), st::contactsAdd.height + st::newGroupNamePadding.top() + _filter.height() + st::newGroupNamePadding.bottom());
@@ -961,14 +1055,17 @@ void ContactsBox::hideAll() {
void ContactsBox::showAll() {
ItemListBox::showAll();
_addContact.show();
_filter.show();
if (_inner.chat() || _inner.creatingChat()) {
_next.show();
_addContact.hide();
} else {
_next.hide();
_addContact.show();
if (_inner.bot()) {
_addContact.hide();
} else {
_addContact.show();
}
}
_cancel.show();
}
@@ -1010,6 +1107,8 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
// paint button sep
p.fillRect(st::btnSelectCancel.width, size().height() - st::btnSelectCancel.height, st::lineWidth, st::btnSelectCancel.height, st::btnSelectSep->b);
} else if (_inner.bot()) {
paintTitle(p, lang(lng_bot_choose_group), true);
} else {
paintTitle(p, lang(lng_contacts_header), true);
}

View File

@@ -30,6 +30,7 @@ public:
ContactsInner(bool creatingChat);
ContactsInner(ChatData *chat);
ContactsInner(UserData *bot);
void init();
void paintEvent(QPaintEvent *e);
@@ -39,7 +40,7 @@ public:
void mousePressEvent(QMouseEvent *e);
void resizeEvent(QResizeEvent *e);
void paintDialog(QPainter &p, UserData *user, ContactData *data, bool sel);
void paintDialog(QPainter &p, PeerData *peer, ContactData *data, bool sel);
void updateFilter(QString filter = QString());
void selectSkip(int32 dir);
@@ -59,6 +60,7 @@ public:
void refresh();
ChatData *chat() const;
UserData *bot() const;
bool creatingChat() const;
~ContactsInner();
@@ -75,11 +77,17 @@ public slots:
void updateSel();
void peerUpdated(PeerData *peer);
void onPeerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars);
void onAddBot();
private:
ChatData *_chat;
UserData *_bot;
bool _creatingChat;
ChatData *_addToChat;
int32 _time;
@@ -99,7 +107,7 @@ private:
bool inchat;
bool check;
};
typedef QMap<UserData*, ContactData*> ContactsData;
typedef QMap<PeerData*, ContactData*> ContactsData;
ContactsData _contactsData;
ContactData *contactData(DialogRow *row);
@@ -125,6 +133,7 @@ public:
ContactsBox(bool creatingChat = false);
ContactsBox(ChatData *chat);
ContactsBox(UserData *bot);
void keyPressEvent(QKeyEvent *e);
void paintEvent(QPaintEvent *e);
void resizeEvent(QResizeEvent *e);

View File

@@ -17,9 +17,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
*/
#pragma once
static const int32 AppVersion = 8017;
static const wchar_t *AppVersionStr = L"0.8.17";
static const bool DevChannel = false;
static const int32 AppVersion = 8027;
static const wchar_t *AppVersionStr = L"0.8.27";
static const bool DevChannel = true;
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
static const wchar_t *AppName = L"Telegram Desktop";
@@ -40,8 +40,11 @@ enum {
MTPContainerLives = 600, // container lives 10 minutes in haveSent map
MTPMinReceiveDelay = 4000, // 4 seconds
MTPMaxReceiveDelay = 64000, // 64 seconds
MTPMinConnectDelay = 1000, // tcp connect should take less then 1 second
MTPMaxConnectDelay = 8000, // tcp connect should take 8 seconds max
MTPConnectionOldTimeout = 192000, // 192 seconds
MTPTcpConnectionWaitTimeout = 3000, // 3 seconds waiting for tcp, until we accept http
MTPTcpConnectionWaitTimeout = 2000, // 2 seconds waiting for tcp, until we accept http
MTPIPv4ConnectionWaitTimeout = 1000, // 1 seconds waiting for ipv4, until we accept ipv6
MTPMillerRabinIterCount = 30, // 30 Miller-Rabin iterations for dh_prime primality check
MTPUploadSessionsCount = 4, // max 4 upload sessions is created
@@ -86,12 +89,16 @@ enum {
AudioCheckPositionDelta = 4800, // update position called each 4800 samples
AudioFadeTimeout = 10, // 10ms
AudioFadeDuration = 500,
AudioVoiceMsgSkip = 400, // 200ms
AudioVoiceMsgFade = 300, // 300ms
AudioPreloadSamples = 5 * 48000, // preload next part if less than 5 seconds remains
AudioVoiceMsgFrequency = 48000, // 48 kHz
AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
AudioVoiceMsgUpdateView = 100, // 100ms
AudioVoiceMsgChannels = 2, // stereo
AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers
AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded
AudioSuspendTimeout = 3000, // suspend in 3 secs after playing is over
AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over
StickerInMemory = 256 * 1024, // 128 Kb stickers hold in memory, auto loaded and displayed inline
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker
@@ -119,7 +126,7 @@ enum {
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
HiddenIsOnlineAfterMessage = 30, // user with hidden last seen stays online for such amount of seconds in the interface
SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface
ServiceUserId = 777000,
WebPageUserId = 701000,
@@ -180,10 +187,24 @@ static const BuiltInDc _builtInDcs[] = {
{ 5, "149.154.171.5", 443 }
};
static const BuiltInDc _builtInDcsIPv6[] = {
{ 1, "2001:b28:f23d:f001::a", 443 },
{ 2, "2001:67c:4e8:f002::a", 443 },
{ 3, "2001:b28:f23d:f003::a", 443 },
{ 4, "2001:67c:4e8:f004::a", 443 },
{ 5, "2001:b28:f23f:f005::a", 443 }
};
static const BuiltInDc _builtInTestDcs[] = {
{ 1, "149.154.175.10", 443 },
{ 2, "149.154.167.40", 443 },
{ 3, "174.140.142.5", 443 }
{ 3, "149.154.175.117", 443 }
};
static const BuiltInDc _builtInTestDcsIPv6[] = {
{ 1, "2001:b28:f23d:f001::e", 443 },
{ 2, "2001:67c:4e8:f002::e", 443 },
{ 3, "2001:b28:f23d:f003::e", 443 }
};
inline const BuiltInDc *builtInDcs() {
@@ -194,6 +215,14 @@ inline int builtInDcsCount() {
return (cTestMode() ? sizeof(_builtInTestDcs) : sizeof(_builtInDcs)) / sizeof(BuiltInDc);
}
inline const BuiltInDc *builtInDcsIPv6() {
return cTestMode() ? _builtInTestDcsIPv6 : _builtInDcsIPv6;
}
inline int builtInDcsCountIPv6() {
return (cTestMode() ? sizeof(_builtInTestDcsIPv6) : sizeof(_builtInDcsIPv6)) / sizeof(BuiltInDc);
}
static const char *UpdatesPublicKey = "\
-----BEGIN RSA PUBLIC KEY-----\n\
MIGJAoGBAMA4ViQrjkPZ9xj0lrer3r23JvxOnrtE8nI69XLGSr+sRERz9YnUptnU\n\

View File

@@ -28,9 +28,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "localstorage.h"
DialogsListWidget::DialogsListWidget(QWidget *parent, MainWidget *main) : QWidget(parent),
dialogs(false),
contactsNoDialogs(true),
contacts(true),
dialogs(DialogsSortByDate),
contactsNoDialogs(DialogsSortByName),
contacts(DialogsSortByName),
sel(0),
contactSel(false),
selByMouse(false),
@@ -64,6 +64,8 @@ int32 DialogsListWidget::searchedOffset() const {
}
void DialogsListWidget::paintEvent(QPaintEvent *e) {
if (!App::main()) return;
QRect r(e->rect());
bool trivial = (rect() == r);
@@ -95,7 +97,7 @@ void DialogsListWidget::paintEvent(QPaintEvent *e) {
}
p.translate(0, from * st::mentionHeight);
if (from < hashtagResults.size()) {
int32 to = (r.bottom() / int32(st::mentionHeight)) + 1, w = width();
int32 to = (r.bottom() / int32(st::mentionHeight)) + 1, w = width(), htagwidth = w - st::dlgPaddingHor * 2;
if (to > hashtagResults.size()) to = hashtagResults.size();
p.setFont(st::mentionFont->f);
p.setPen(st::black->p);
@@ -106,8 +108,27 @@ void DialogsListWidget::paintEvent(QPaintEvent *e) {
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
p.drawPixmap(QPoint(w - st::notifyClose.icon.pxWidth() - skip, skip), App::sprite(), st::notifyClose.icon);
}
QString tag = st::mentionFont->m.elidedText('#' + hashtagResults.at(from), Qt::ElideRight, w - st::dlgPaddingHor * 2);
p.drawText(st::dlgPaddingHor, st::mentionTop + st::mentionFont->ascent, tag);
QString first = (_hashtagFilter.size() < 2) ? QString() : ('#' + hashtagResults.at(from).mid(0, _hashtagFilter.size() - 1)), second = (_hashtagFilter.size() < 2) ? ('#' + hashtagResults.at(from)) : hashtagResults.at(from).mid(_hashtagFilter.size() - 1);
int32 firstwidth = st::mentionFont->m.width(first), secondwidth = st::mentionFont->m.width(second);
if (htagwidth < firstwidth + secondwidth) {
if (htagwidth < firstwidth + st::mentionFont->elidew) {
first = st::mentionFont->m.elidedText(first + second, Qt::ElideRight, htagwidth);
second = QString();
} else {
second = st::mentionFont->m.elidedText(second, Qt::ElideRight, htagwidth - firstwidth);
}
}
p.setFont(st::mentionFont->f);
if (!first.isEmpty()) {
p.setPen(st::profileOnlineColor->p);
p.drawText(st::dlgPaddingHor, st::mentionTop + st::mentionFont->ascent, first);
}
if (!second.isEmpty()) {
p.setPen(st::profileOfflineColor->p);
p.drawText(st::dlgPaddingHor + firstwidth, st::mentionTop + st::mentionFont->ascent, second);
}
p.translate(0, st::mentionHeight);
}
}
@@ -607,8 +628,9 @@ void DialogsListWidget::onFilterUpdate(QString newFilter, bool force) {
}
}
void DialogsListWidget::onHashtagFilterUpdate(QString newFilter) {
void DialogsListWidget::onHashtagFilterUpdate(QStringRef newFilter) {
if (newFilter.isEmpty() || newFilter.at(0) != '#') {
_hashtagFilter = QString();
if (!hashtagResults.isEmpty()) {
hashtagResults.clear();
refresh(true);
@@ -616,6 +638,7 @@ void DialogsListWidget::onHashtagFilterUpdate(QString newFilter) {
}
return;
}
_hashtagFilter = newFilter.toString();
if (cRecentSearchHashtags().isEmpty() && cRecentWriteHashtags().isEmpty()) {
Local::readRecentHashtags();
}
@@ -624,7 +647,7 @@ void DialogsListWidget::onHashtagFilterUpdate(QString newFilter) {
if (!recent.isEmpty()) {
hashtagResults.reserve(qMin(recent.size(), 5));
for (RecentHashtagPack::const_iterator i = recent.cbegin(), e = recent.cend(); i != e; ++i) {
if (i->first.startsWith(newFilter.midRef(1), Qt::CaseInsensitive) && i->first.size() + 1 != newFilter.size()) {
if (i->first.startsWith(_hashtagFilter.midRef(1), Qt::CaseInsensitive) && i->first.size() + 1 != newFilter.size()) {
hashtagResults.push_back(i->first);
if (hashtagResults.size() == 5) break;
}
@@ -1604,7 +1627,10 @@ void DialogsWidget::onSearchMore(MsgId minMsgId) {
void DialogsWidget::loadDialogs() {
if (dlgPreloading) return;
if (dlgCount >= 0 && dlgOffset >= dlgCount) return;
if (dlgCount >= 0 && dlgOffset >= dlgCount) {
cSetDialogsReceived(true);
return;
}
int32 loadCount = dlgOffset ? DialogsPerPage : DialogsFirstLoad;
dlgPreloading = MTP::send(MTPmessages_GetDialogs(MTP_int(dlgOffset), MTP_int(0), MTP_int(loadCount)), rpcDone(&DialogsWidget::dialogsReceived), rpcFail(&DialogsWidget::dialogsFailed));
@@ -1752,12 +1778,13 @@ void DialogsWidget::onFilterUpdate(bool force) {
void DialogsWidget::onFilterCursorMoved(int from, int to) {
if (to < 0) to = _filter.cursorPosition();
QString t = _filter.text(), r;
QString t = _filter.text();
QStringRef r;
for (int start = to; start > 0;) {
--start;
if (t.size() <= start) break;
if (t.at(start) == '#') {
r = t.mid(start, to - start);
r = t.midRef(start, to - start);
break;
}
if (!t.at(start).isLetterOrNumber() && t.at(start) != '_') break;
@@ -1886,6 +1913,10 @@ DialogsIndexed &DialogsWidget::contactsList() {
return list.contactsList();
}
DialogsIndexed &DialogsWidget::dialogsList() {
return list.dialogsList();
}
void DialogsWidget::onAddContact() {
App::wnd()->replaceLayer(new AddContactBox());
}

View File

@@ -94,7 +94,7 @@ public:
bool hasFilteredResults() const;
void onFilterUpdate(QString newFilter, bool force = false);
void onHashtagFilterUpdate(QString newFilter);
void onHashtagFilterUpdate(QStringRef newFilter);
void itemRemoved(HistoryItem *item);
void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem);
@@ -130,7 +130,7 @@ private:
bool contactSel;
bool selByMouse;
QString filter;
QString filter, _hashtagFilter;
QStringList hashtagResults;
int32 hashtagSel;
@@ -197,6 +197,7 @@ public:
void removeContact(UserData *user);
DialogsIndexed &contactsList();
DialogsIndexed &dialogsList();
void enableShadow(bool enable = true);

File diff suppressed because it is too large Load Diff

View File

@@ -269,6 +269,16 @@ private:
int32 _stickersWidth;
};
struct StickerIcon {
StickerIcon() : setId(RecentStickerSetId), sticker(0), pixw(0), pixh(0) {
}
StickerIcon(uint64 setId, DocumentData *sticker, int32 pixw, int32 pixh) : setId(setId), sticker(sticker), pixw(pixw), pixh(pixh) {
}
uint64 setId;
DocumentData *sticker;
int32 pixw, pixh;
};
class StickerPanInner : public TWidget, public Animated {
Q_OBJECT
@@ -294,9 +304,13 @@ public:
void refreshStickers();
void refreshRecent(bool resize = true);
void fillIcons(QVector<StickerIcon> &icons);
void setScrollTop(int top);
void preloadImages();
uint64 currentSet(int yOffset) const;
public slots:
void updateSelected();
@@ -306,6 +320,8 @@ signals:
void selected(DocumentData *sticker);
void removing(uint64 setId);
void refreshIcons();
void switchToEmoji();
void scrollToY(int y);
@@ -327,6 +343,7 @@ private:
QList<uint64> _setIds;
QList<StickerPack> _sets;
QList<QVector<float64> > _hovers;
QList<bool> _custom;
int32 _selected, _pressedSel;
QPoint _lastMousePos;
@@ -349,6 +366,12 @@ public:
void otherEnter();
void otherLeave();
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
bool event(QEvent *e);
void fastHide();
bool hiding() const {
return _hiding || _hideTimer.isActive();
@@ -356,6 +379,8 @@ public:
bool animStep(float64 ms);
bool iconAnim(float64 ms);
bool eventFilter(QObject *obj, QEvent *e);
void stickersInstalled(uint64 setId);
@@ -377,6 +402,8 @@ public slots:
void onRemoveSetSure();
void onDelayedHide();
void onRefreshIcons();
signals:
void emojiSelected(EmojiPtr emoji);
@@ -385,6 +412,13 @@ signals:
private:
bool _horizontal;
void leaveToChildEvent(QEvent *e);
void updateSelected();
void updateIcons();
void prepareTab(int32 &left, int32 top, int32 _width, FlatRadiobutton &tab);
void showAll();
@@ -403,6 +437,18 @@ private:
BoxShadow _shadow;
FlatRadiobutton _recent, _people, _nature, _food, _celebration, _activity, _travel, _objects;
QVector<StickerIcon> _icons;
QVector<float64> _iconHovers;
int32 _iconOver, _iconSel, _iconDown;
bool _iconsDragging;
typedef QMap<int32, uint64> Animations; // index - showing, -index - hiding
Animations _iconAnimations;
Animation _iconAnim;
QPoint _iconsMousePos, _iconsMouseDown;
int32 _iconsLeft, _iconsTop;
int32 _iconsStartX, _iconsMax;
anim::ivalue _iconsX;
uint64 _iconsStartAnim;
bool _stickersShown;
QPixmap _fromCache, _toCache;
@@ -421,6 +467,7 @@ private:
typedef QList<UserData*> MentionRows;
typedef QList<QString> HashtagRows;
typedef QList<QPair<UserData*, BotCommand> > BotCommandRows;
class MentionsDropdown;
class MentionsInner : public QWidget {
@@ -428,7 +475,7 @@ class MentionsInner : public QWidget {
public:
MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows);
MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows, BotCommandRows *crows);
void paintEvent(QPaintEvent *e);
@@ -459,6 +506,7 @@ private:
MentionsDropdown *_parent;
MentionRows *_rows;
HashtagRows *_hrows;
BotCommandRows *_crows;
int32 _sel;
bool _mouseSel;
QPoint _mousePos;
@@ -477,13 +525,14 @@ public:
void fastHide();
void showFiltered(ChatData *chat, QString start);
void showFiltered(PeerData *peer, QString start);
void updateFiltered(bool toDown = false);
void setBoundings(QRect boundings);
bool animStep(float64 ms);
const QString &filter() const;
ChatData *chat() const;
int32 innerTop();
int32 innerBottom();
@@ -510,11 +559,13 @@ private:
QPixmap _cache;
MentionRows _rows;
HashtagRows _hrows;
BotCommandRows _crows;
ScrollArea _scroll;
MentionsInner _inner;
ChatData *_chat;
UserData *_user;
QString _filter;
QRect _boundings;

View File

@@ -40,6 +40,10 @@ void FileUploader::uploadMedia(MsgId msgId, const ReadyLocalMedia &media) {
if (!media.file.isEmpty()) {
document->location = FileLocation(StorageFilePartial, media.file);
}
} else if (media.type == ToPrepareAudio) {
AudioData *audio = App::feedAudio(media.audio);
audio->status = FileUploading;
audio->data = media.data;
}
queue.insert(msgId, File(media));
sendNext();
@@ -56,6 +60,12 @@ void FileUploader::currentFailed() {
doc->status = FileFailed;
}
emit documentFailed(j.key());
} else if (j->media.type == ToPrepareAudio) {
AudioData *audio = App::audio(j->media.id);
if (audio->status == FileUploading) {
audio->status = FileFailed;
}
emit audioFailed(j.key());
}
queue.erase(j);
}
@@ -112,7 +122,7 @@ void FileUploader::sendNext() {
emit photoReady(uploading, MTP_inputFile(MTP_long(i->media.id), MTP_int(i->partsCount), MTP_string(i->media.filename), MTP_string(i->media.jpeg_md5)));
} else if (i->media.type == ToPrepareDocument) {
QByteArray docMd5(32, Qt::Uninitialized);
hashMd5Hex(i->docHash.result(), docMd5.data());
hashMd5Hex(i->md5Hash.result(), docMd5.data());
MTPInputFile doc = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename)) : MTP_inputFile(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename), MTP_string(docMd5));
if (i->partsCount) {
@@ -120,6 +130,12 @@ void FileUploader::sendNext() {
} else {
emit documentReady(uploading, doc);
}
} else if (i->media.type == ToPrepareAudio) {
QByteArray audioMd5(32, Qt::Uninitialized);
hashMd5Hex(i->md5Hash.result(), audioMd5.data());
MTPInputFile audio = (i->docSize > UseBigFilesFrom) ? MTP_inputFileBig(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename)) : MTP_inputFile(MTP_long(i->media.id), MTP_int(i->docPartsCount), MTP_string(i->media.filename), MTP_string(audioMd5));
emit audioReady(uploading, audio);
}
queue.remove(uploading);
uploading = 0;
@@ -139,12 +155,12 @@ void FileUploader::sendNext() {
}
toSend = i->docFile->read(i->docPartSize);
if (i->docSize <= UseBigFilesFrom) {
i->docHash.feed(toSend.constData(), toSend.size());
i->md5Hash.feed(toSend.constData(), toSend.size());
}
} else {
toSend = i->media.data.mid(i->docSentParts * i->docPartSize, i->docPartSize);
if (i->media.type == ToPrepareDocument && i->docSentParts <= UseBigFilesFrom) {
i->docHash.feed(toSend.constData(), toSend.size());
if ((i->media.type == ToPrepareDocument || i->media.type == ToPrepareAudio) && i->docSentParts <= UseBigFilesFrom) {
i->md5Hash.feed(toSend.constData(), toSend.size());
}
}
if (toSend.size() > i->docPartSize || (toSend.size() < i->docPartSize && i->docSentParts + 1 != i->docPartsCount)) {
@@ -249,6 +265,14 @@ void FileUploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
}
}
emit documentProgress(k.key());
} else if (k->media.type == ToPrepareAudio) {
AudioData *audio = App::audio(k->media.id);
if (audio->status == FileUploading) {
audio->uploadOffset = (k->docSentParts - docRequestsSent.size()) * k->docPartSize;
if (audio->uploadOffset > audio->size) {
audio->uploadOffset = audio->size;
}
}
}
}
}

View File

@@ -45,19 +45,22 @@ signals:
void photoReady(MsgId msgId, const MTPInputFile &file);
void documentReady(MsgId msgId, const MTPInputFile &file);
void thumbDocumentReady(MsgId msgId, const MTPInputFile &file, const MTPInputFile &thumb);
void audioReady(MsgId msgId, const MTPInputFile &file);
void photoProgress(MsgId msgId);
void documentProgress(MsgId msgId);
void audioProgress(MsgId msgId);
void photoFailed(MsgId msgId);
void documentFailed(MsgId msgId);
void audioFailed(MsgId msgId);
private:
struct File {
File(const ReadyLocalMedia &media) : media(media), docSentParts(0) {
partsCount = media.parts.size();
if (media.type == ToPrepareDocument) {
if (media.type == ToPrepareDocument || media.type == ToPrepareAudio) {
docSize = media.file.isEmpty() ? media.data.size() : media.filesize;
if (docSize >= 1024 * 1024 || !setPartSize(DocumentUploadPartSize0)) {
if (docSize > 32 * 1024 * 1024 || !setPartSize(DocumentUploadPartSize1)) {
@@ -83,12 +86,13 @@ private:
ReadyLocalMedia media;
int32 partsCount;
HashMd5 md5Hash;
QSharedPointer<QFile> docFile;
int32 docSentParts;
int32 docSize;
int32 docPartSize;
int32 docPartsCount;
HashMd5 docHash;
};
typedef QMap<MsgId, File> Queue;

View File

@@ -223,6 +223,63 @@ private:
};
class AnimationFunc {
public:
virtual bool animStep(float64 ms) = 0;
virtual ~AnimationFunc() {
}
};
template <typename Type>
class AnimationFuncOwned : public AnimationFunc {
public:
typedef bool (Type::*Method)(float64);
AnimationFuncOwned(Type *obj, Method method) : _obj(obj), _method(method) {
}
bool animStep(float64 ms) {
return (_obj->*_method)(ms);
}
private:
Type *_obj;
Method _method;
};
template <typename Type>
AnimationFunc *animFunc(Type *obj, typename AnimationFuncOwned<Type>::Method method) {
return new AnimationFuncOwned<Type>(obj, method);
}
class Animation : public Animated {
public:
Animation(AnimationFunc *func) : _func(func) {
}
void start() {
anim::start(this);
}
void stop() {
anim::stop(this);
}
//Animation
bool animStep(float64 ms) {
return _func->animStep(ms);
}
~Animation() {
delete _func;
}
private:
AnimationFunc *_func;
};
class AnimationManager : public QObject {
Q_OBJECT

View File

@@ -19,10 +19,11 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "boxshadow.h"
BoxShadow::BoxShadow(const style::rect &topLeft) : _size(topLeft.width() / cIntRetinaFactor()) {
BoxShadow::BoxShadow(const style::sprite &topLeft) : _size(topLeft.pxWidth()), _pixsize(_size * cIntRetinaFactor()) {
if (!_size) return;
QImage cornersImage(_size * 2, _size * 2, QImage::Format_ARGB32_Premultiplied);
QImage cornersImage(_pixsize * 2, _pixsize * 2, QImage::Format_ARGB32_Premultiplied);
cornersImage.setDevicePixelRatio(cRetinaFactor());
{
QPainter p(&cornersImage);
p.drawPixmap(QPoint(rtl() ? _size : 0, 0), App::sprite(), topLeft);
@@ -41,39 +42,65 @@ BoxShadow::BoxShadow(const style::rect &topLeft) : _size(topLeft.width() / cIntR
{
QPainter p(&cornersImage);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(0, _size, cornersImage.mirrored(), 0, _size, _size, _size);
QImage m = cornersImage.mirrored();
m.setDevicePixelRatio(cRetinaFactor());
p.drawImage(0, _size, m, 0, _pixsize, _pixsize, _pixsize);
}
{
QPainter p(&cornersImage);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(_size, 0, cornersImage.mirrored(true, false), _size, 0, _size, _size * 2);
QImage m = cornersImage.mirrored(true, false);
m.setDevicePixelRatio(cRetinaFactor());
p.drawImage(_size, 0, m, _pixsize, 0, _pixsize, _pixsize * 2);
}
_corners = QPixmap::fromImage(cornersImage, Qt::ColorOnly);
_colors.reserve(_size);
_corners.setDevicePixelRatio(cRetinaFactor());
_colors.reserve(_pixsize);
uchar prev = 0;
for (int32 i = 0; i < _size; ++i) {
uchar a = (cornersImage.pixel(QPoint(i, _size - 1)) >> 24);
for (int32 i = 0; i < _pixsize; ++i) {
uchar a = (cornersImage.pixel(QPoint(i, _pixsize - 1)) >> 24);
if (a < prev) break;
_colors.push_back(style::color(0, 0, 0, a));
prev = a;
}
}
void BoxShadow::paint(QPainter &p, const QRect &box, const QPoint &shift, int32 flags) {
if (!_size) return;
int32 count = _colors.size(), minus = _size - count + 1;
bool left = (flags & Left), top = (flags & Top), right = (flags & Right), bottom = (flags & Bottom);
if (left && top) p.drawPixmap(box.left() - _size + minus + shift.x(), box.top() - _size + minus + shift.y(), _corners, 0, 0, _size, _size);
if (right && top) p.drawPixmap(box.right() - minus + 1 + shift.x(), box.top() - _size + minus + shift.y(), _corners, _size, 0, _size, _size);
if (right && bottom) p.drawPixmap(box.right() - minus + 1 + shift.x(), box.bottom() - minus + 1 + shift.y(), _corners, _size, _size, _size, _size);
if (left && bottom) p.drawPixmap(box.left() - _size + minus + shift.x(), box.bottom() - minus + 1 + shift.y(), _corners, 0, _size, _size, _size);
for (int32 i = 1; i <= count; ++i) {
p.setPen(_colors[i - 1]->p);
if (top) p.fillRect(box.left() + (left ? minus : 0) + shift.x(), box.top() - count + i + shift.y(), box.width() - (right ? minus : 0) - (left ? minus : 0), cIntRetinaFactor(), _colors[i - 1]->b);
if (right) p.fillRect(box.right() + count - i + shift.x(), box.top() + (top ? minus : 0) + shift.y(), cIntRetinaFactor(), box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i - 1]->b);
if (bottom) p.fillRect(box.left() + (left ? minus : 0) + shift.x(), box.bottom() + count - i + shift.y(), box.width() - (right ? minus : 0) - (left ? minus : 0), cIntRetinaFactor(), _colors[i - 1]->b);
if (left) p.fillRect(box.left() - count + i + shift.x(), box.top() + (top ? minus : 0) + shift.y(), cIntRetinaFactor(), box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i - 1]->b);
if (cRetina()) {
_left = QPixmap::fromImage(cornersImage.copy(0, _pixsize - 1, _colors.size(), 1), Qt::ColorOnly);
_left.setDevicePixelRatio(cRetinaFactor());
_top = QPixmap::fromImage(cornersImage.copy(_pixsize - 1, 0, 1, _colors.size()), Qt::ColorOnly);
_top.setDevicePixelRatio(cRetinaFactor());
_right = QPixmap::fromImage(cornersImage.copy(_pixsize * 2 - _colors.size(), _pixsize, _colors.size(), 1), Qt::ColorOnly);
_right.setDevicePixelRatio(cRetinaFactor());
_bottom = QPixmap::fromImage(cornersImage.copy(_pixsize, _pixsize * 2 - _colors.size(), 1, _colors.size()), Qt::ColorOnly);
_bottom.setDevicePixelRatio(cRetinaFactor());
}
}
void BoxShadow::paint(QPainter &p, const QRect &box, int32 shifty, int32 flags) {
if (!_size) return;
int32 rshifty = shifty * cIntRetinaFactor();
int32 count = _colors.size(), countsize = count / cIntRetinaFactor(), minus = _size - countsize + shifty;
bool left = (flags & Left), top = (flags & Top), right = (flags & Right), bottom = (flags & Bottom);
if (left && top) p.drawPixmap(box.left() - _size + minus, box.top() - _size + minus + shifty, _corners, 0, 0, _pixsize, _pixsize);
if (right && top) p.drawPixmap(box.left() + box.width() - minus, box.top() - _size + minus + shifty, _corners, _pixsize, 0, _pixsize, _pixsize);
if (right && bottom) p.drawPixmap(box.left() + box.width() - minus, box.top() + box.height() - minus + shifty, _corners, _pixsize, _pixsize, _pixsize, _pixsize);
if (left && bottom) p.drawPixmap(box.left() - _size + minus, box.top() + box.height() - minus + shifty, _corners, 0, _pixsize, _pixsize, _pixsize);
if (cRetina()) {
bool wasSmooth = p.renderHints().testFlag(QPainter::SmoothPixmapTransform);
if (wasSmooth) p.setRenderHint(QPainter::SmoothPixmapTransform, false);
if (left) p.drawPixmap(box.left() - countsize + shifty, box.top() + (top ? minus : 0) + shifty, countsize - shifty, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _left, 0, 0, count - rshifty, 1);
if (top) p.drawPixmap(box.left() + (left ? minus : 0), box.top() - countsize + 2 * shifty, box.width() - (right ? minus : 0) - (left ? minus : 0), countsize - 2 * shifty, _top, 0, 0, 1, count - 2 * rshifty);
if (right) p.drawPixmap(box.left() + box.width(), box.top() + (top ? minus : 0) + shifty, countsize - shifty, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _right, rshifty, 0, count - rshifty, 1);
if (bottom) p.drawPixmap(box.left() + (left ? minus : 0), box.top() + box.height(), box.width() - (right ? minus : 0) - (left ? minus : 0), countsize, _bottom, 0, 0, 1, count);
if (wasSmooth) p.setRenderHint(QPainter::SmoothPixmapTransform);
} else {
p.setPen(Qt::NoPen);
for (int32 i = 0; i < count; ++i) {
if (left && i + shifty < count) p.fillRect(box.left() - count + i + shifty, box.top() + (top ? minus : 0) + shifty, 1, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i]->b);
if (top && i + 2 * shifty < count) p.fillRect(box.left() + (left ? minus : 0), box.top() - count + i + 2 * shifty, box.width() - (right ? minus : 0) - (left ? minus : 0), 1, _colors[i]->b);
if (right && i + shifty < count) p.fillRect(box.left() + box.width() + count - i - shifty - 1, box.top() + (top ? minus : 0) + shifty, 1, box.height() - (bottom ? minus : 0) - (top ? minus : 0), _colors[i]->b);
if (bottom) p.fillRect(box.left() + (left ? minus : 0), box.top() + box.height() + count - i - 1, box.width() - (right ? minus : 0) - (left ? minus : 0), 1, _colors[i]->b);
}
}
}

View File

@@ -27,14 +27,14 @@ public:
Bottom = 8
};
BoxShadow(const style::rect &topLeft);
BoxShadow(const style::sprite &topLeft);
void paint(QPainter &p, const QRect &box, const QPoint &shift = QPoint(0, 1), int32 flags = Left | Top | Right | Bottom);
void paint(QPainter &p, const QRect &box, int32 shifty, int32 flags = Left | Top | Right | Bottom);
private:
int32 _size;
QPixmap _corners;
int32 _size, _pixsize;
QPixmap _corners, _left, _top, _right, _bottom;
QVector<style::color> _colors;
};

View File

@@ -144,7 +144,7 @@ void ContextMenu::paintEvent(QPaintEvent *e) {
QRect r(_st.padding.left(), _st.padding.top(), _width - _st.padding.left() - _st.padding.right(), _height - _st.padding.top() - _st.padding.bottom());
// draw shadow
_shadow.paint(p, r);
_shadow.paint(p, r, _st.shadowShift);
}
void ContextMenu::keyPressEvent(QKeyEvent *e) {

View File

@@ -485,7 +485,7 @@ void CountrySelect::paintEvent(QPaintEvent *e) {
p.setOpacity(1);
QRect inner(_innerLeft, _innerTop, _innerWidth, _innerHeight);
_shadow.paint(p, inner);
_shadow.paint(p, inner, st::boxShadowShift);
if (trivial || e->rect().intersects(inner)) {
// fill bg
p.fillRect(inner, st::white->b);

View File

@@ -81,9 +81,6 @@ inline EmojiPtr emojiFromUrl(const QString &url) {
}
inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) {
QString tmp(ch, e - ch);
QByteArray tmp2 = tmp.toUtf8();
const char *tmp3 = tmp2.constData();
EmojiPtr emoji = 0;
if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3))) {
uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode();

View File

@@ -20,7 +20,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
FlatButton::FlatButton(QWidget *parent, const QString &text, const style::flatButton &st) : Button(parent),
_text(text),
_st(st),
_st(st), _autoFontPadding(0),
a_bg(st.bgColor->c), a_text(st.color->c), _opacity(1) {
if (_st.width < 0) {
_st.width = textWidth() - _st.width;
@@ -52,10 +52,35 @@ void FlatButton::setWidth(int32 w) {
resize(_st.width, height());
}
void FlatButton::setAutoFontSize(int32 padding, const QString &txt) {
_autoFontPadding = padding;
if (_autoFontPadding) {
_textForAutoSize = txt;
resizeEvent(0);
} else {
_textForAutoSize = QString();
_autoFont = style::font();
}
update();
}
int32 FlatButton::textWidth() const {
return _st.font->m.width(_text);
}
void FlatButton::resizeEvent(QResizeEvent *e) {
if (_autoFontPadding) {
_autoFont = _st.font;
for (int32 s = _st.font->f.pixelSize(); s >= st::fsize; --s) {
_autoFont = style::font(s, _st.font->flags(), _st.font->family());
if (2 * _autoFontPadding + _autoFont->m.width(_textForAutoSize) <= width()) {
break;
}
}
}
return Button::resizeEvent(e);
}
bool FlatButton::animStep(float64 ms) {
float64 dt = ms / _st.duration;
bool res = true;
@@ -95,12 +120,15 @@ void FlatButton::paintEvent(QPaintEvent *e) {
p.setOpacity(_opacity);
p.fillRect(r, a_bg.current());
p.setFont(((_state & StateOver) ? _st.overFont : _st.font)->f);
p.setFont((_autoFont ? _autoFont : ((_state & StateOver) ? _st.overFont : _st.font))->f);
p.setRenderHint(QPainter::TextAntialiasing);
p.setPen(a_text.current());
r.setTop((_state & StateOver) ? ((_state & StateDown) ? _st.downTextTop : _st.overTextTop) : _st.textTop);
p.drawText(r, _text, QTextOption(Qt::AlignHCenter));
int32 top = (_state & StateOver) ? ((_state & StateDown) ? _st.downTextTop : _st.overTextTop) : _st.textTop;
if (_autoFont) top += (_st.font->height - _autoFont->height) / 2;
r.setTop(top);
p.drawText(r, _text, style::al_top);
}
BottomButton::BottomButton(QWidget *w, const QString &t, const style::flatButton &s) : FlatButton(w, t, s) {

View File

@@ -29,12 +29,15 @@ public:
FlatButton(QWidget *parent, const QString &text, const style::flatButton &st);
void resizeEvent(QResizeEvent *e);
bool animStep(float64 ms);
void paintEvent(QPaintEvent *e);
void setOpacity(float64 o);
void setText(const QString &text);
void setWidth(int32 w);
void setAutoFontSize(int32 padding, const QString &txt);
int32 textWidth() const;
@@ -47,12 +50,15 @@ public slots:
private:
QString _text;
QString _text, _textForAutoSize;
int32 _textWidth;
style::flatButton _st;
anim::cvalue a_bg, a_text;
int32 _autoFontPadding;
style::font _autoFont;
anim::cvalue a_bg, a_text;
float64 _opacity;
};

View File

@@ -179,7 +179,7 @@ EmojiPtr FlatTextarea::getSingleEmoji() const {
return 0;
}
void FlatTextarea::getMentionHashtagStart(QString &start) const {
void FlatTextarea::getMentionHashtagBotCommandStart(QString &start) const {
int32 pos = textCursor().position();
if (textCursor().anchor() != pos) return;
@@ -195,11 +195,16 @@ void FlatTextarea::getMentionHashtagStart(QString &start) const {
QTextCharFormat f = fr.charFormat();
if (f.isImageFormat()) continue;
bool mentionInCommand = false;
QString t(fr.text());
for (int i = pos - p; i > 0; --i) {
if (t.at(i - 1) == '@') {
if ((pos - p - i < 1 || t.at(i).isLetter()) && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
start = t.mid(i - 1, pos - p - i + 1);
} else if ((pos - p - i < 1 || t.at(i).isLetter()) && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
mentionInCommand = true;
--i;
continue;
}
return;
} else if (t.at(i - 1) == '#') {
@@ -207,15 +212,20 @@ void FlatTextarea::getMentionHashtagStart(QString &start) const {
start = t.mid(i - 1, pos - p - i + 1);
}
return;
} else if (t.at(i - 1) == '/') {
if (i < 2) {
start = t.mid(i - 1, pos - p - i + 1);
}
return;
}
if (pos - p - i > 63) break;
if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break;
if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break;
}
return;
}
}
void FlatTextarea::onMentionOrHashtagInsert(QString mentionOrHashtag) {
void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
QTextCursor c(textCursor());
int32 pos = c.position();
@@ -231,31 +241,37 @@ void FlatTextarea::onMentionOrHashtagInsert(QString mentionOrHashtag) {
QTextCharFormat f = fr.charFormat();
if (f.isImageFormat()) continue;
bool mentionInCommand = false;
QString t(fr.text());
for (int i = pos - p; i > 0; --i) {
if (t.at(i - 1) == '@' || t.at(i - 1) == '#') {
if (t.at(i - 1) == '@' || t.at(i - 1) == '#' || t.at(i - 1) == '/') {
if ((i == pos - p || t.at(i).isLetter() || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
c.setPosition(p + i - 1, QTextCursor::MoveAnchor);
int till = p + i;
for (; (till < e) && (till - p - i + 1 < mentionOrHashtag.size()); ++till) {
if (t.at(till - p).toLower() != mentionOrHashtag.at(till - p - i + 1).toLower()) {
for (; (till < e) && (till - p - i + 1 < str.size()); ++till) {
if (t.at(till - p).toLower() != str.at(till - p - i + 1).toLower()) {
break;
}
}
if (till - p - i + 1 == mentionOrHashtag.size() && till < e && t.at(till - p) == ' ') {
if (till - p - i + 1 == str.size() && till < e && t.at(till - p) == ' ') {
++till;
}
c.setPosition(till, QTextCursor::KeepAnchor);
c.insertText(mentionOrHashtag + ' ');
c.insertText(str + ' ');
return;
} else if ((i == pos - p || t.at(i).isLetter()) && t.at(i - 1) == '@' && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
mentionInCommand = true;
--i;
continue;
}
break;
}
if (pos - p - i > 63) break;
if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break;
if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break;
}
break;
}
c.insertText(mentionOrHashtag + ' ');
c.insertText(str + ' ');
}
void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const {
@@ -681,8 +697,12 @@ bool FlatTextarea::animStep(float64 ms) {
return res;
}
const QString &FlatTextarea::getLastText() const {
return _oldtext;
}
void FlatTextarea::updatePlaceholder() {
bool vis = !hasText();
bool vis = getLastText().isEmpty();
if (vis == _phVisible) return;
a_phLeft.start(vis ? 0 : _st.phShift);

View File

@@ -37,6 +37,7 @@ public:
void resizeEvent(QResizeEvent *e);
void mousePressEvent(QMouseEvent *e);
const QString &getLastText() const;
void updatePlaceholder();
QRect getTextRect() const;
@@ -48,7 +49,7 @@ public:
QSize minimumSizeHint() const;
EmojiPtr getSingleEmoji() const;
void getMentionHashtagStart(QString &start) const;
void getMentionHashtagBotCommandStart(QString &start) const;
void removeSingleEmoji();
QString getText(int32 start = 0, int32 end = -1) const;
bool hasText() const;
@@ -71,7 +72,7 @@ public slots:
void onUndoAvailable(bool avail);
void onRedoAvailable(bool avail);
void onMentionOrHashtagInsert(QString mentionOrHashtag);
void onMentionHashtagOrBotCommandInsert(QString str);
signals:
@@ -85,6 +86,22 @@ signals:
protected:
void insertEmoji(EmojiPtr emoji, QTextCursor c);
TWidget *tparent() {
return qobject_cast<TWidget*>(parentWidget());
}
const TWidget *tparent() const {
return qobject_cast<const TWidget*>(parentWidget());
}
void enterEvent(QEvent *e) {
TWidget *p(tparent());
if (p) p->leaveToChildEvent(e);
return QTextEdit::enterEvent(e);
}
void leaveEvent(QEvent *e) {
TWidget *p(tparent());
if (p) p->enterFromChildEvent(e);
return QTextEdit::leaveEvent(e);
}
private:

View File

@@ -596,6 +596,9 @@ void ScrollArea::enterEvent(QEvent *e) {
hor.hideTimeout(_st.hiding);
vert.hideTimeout(_st.hiding);
}
TWidget *p(tparent());
if (p) p->leaveToChildEvent(e);
return QScrollArea::enterEvent(e);
}
void ScrollArea::leaveEvent(QEvent *e) {
@@ -603,6 +606,9 @@ void ScrollArea::leaveEvent(QEvent *e) {
hor.hideTimeout(0);
vert.hideTimeout(0);
}
TWidget *p(tparent());
if (p) p->enterFromChildEvent(e);
return QScrollArea::leaveEvent(e);
}
void ScrollArea::scrollToY(int toTop, int toBottom) {
@@ -655,3 +661,7 @@ void ScrollArea::updateColors(const style::color &bar, const style::color &bg, c
hor.update();
vert.update();
}
ScrollArea::~ScrollArea() {
takeWidget();
}

View File

@@ -131,6 +131,8 @@ public:
void updateColors(const style::color &bar, const style::color &bg, const style::color &barOver, const style::color &bgOver);
~ScrollArea();
public slots:
void scrollToY(int toTop, int toBottom = -1);
@@ -150,6 +152,12 @@ signals:
protected:
void scrollContentsBy(int dx, int dy);
TWidget *tparent() {
return qobject_cast<TWidget*>(parentWidget());
}
const TWidget *tparent() const {
return qobject_cast<const TWidget*>(parentWidget());
}
private:

View File

@@ -61,6 +61,10 @@ namespace style {
return _flags;
}
uint32 FontData::family() const {
return _family;
}
Font FontData::otherFlagsFont(uint32 flag, bool set) const {
int32 newFlags = set ? (_flags | flag) : (_flags & ~flag);
if (!modified[newFlags].v()) {

View File

@@ -104,6 +104,7 @@ namespace style {
Font underline(bool set = true) const;
uint32 flags() const;
uint32 family() const;
QFont f;
QFontMetrics m;

View File

@@ -30,6 +30,7 @@ namespace {
const QRegularExpression _reMailStart(qsl("^[a-zA-Z\\-_\\.0-9]{1,256}\\@"));
const QRegularExpression _reHashtag(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption);
const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{5,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption);
const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)"));
QSet<int32> _validProtocols, _validTopDomains;
const style::textStyle *_textStyle = 0;
@@ -61,6 +62,10 @@ const QRegularExpression &reHashtag() {
return _reHashtag;
}
const QRegularExpression &reBotCommand() {
return _reBotCommand;
}
const style::textStyle *textstyleCurrent() {
return _textStyle;
}
@@ -302,7 +307,10 @@ public:
}
void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) {
if (!original.isEmpty() && original.at(0) == '@') {
if (!original.isEmpty() && original.at(0) == '/') {
result = original;
fullDisplayed = -4; // bot command
} else if (!original.isEmpty() && original.at(0) == '@') {
result = original;
fullDisplayed = -3; // mention
} else if (!original.isEmpty() && original.at(0) == '#') {
@@ -567,7 +575,9 @@ public:
_t->_links.resize(lnkIndex);
const TextLinkData &data(links[lnkIndex - maxLnkIndex - 1]);
TextLinkPtr lnk;
if (data.fullDisplayed < -2) { // mention
if (data.fullDisplayed < -3) { // bot command
lnk = TextLinkPtr(new BotCommandLink(data.url));
} else if (data.fullDisplayed < -2) { // mention
if (options.flags & TextTwitterMentions) {
lnk = TextLinkPtr(new TextLink(qsl("https://twitter.com/") + data.url.mid(1), true));
} else if (options.flags & TextInstagramMentions) {
@@ -612,7 +622,7 @@ private:
TextLinkData(const QString &url = QString(), int32 fullDisplayed = 1) : url(url), fullDisplayed(fullDisplayed) {
}
QString url;
int32 fullDisplayed; // -3 - mention, -2 - hashtag, -1 - email
int32 fullDisplayed; // -4 - bot command, -3 - mention, -2 - hashtag, -1 - email
};
typedef QVector<TextLinkData> TextLinks;
TextLinks links;
@@ -738,7 +748,15 @@ void TextLink::onClick(Qt::MouseButton button) const {
QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
if (telegramMeUser.hasMatch()) {
App::openUserByName(telegramMeUser.captured(1));
QString params = url.mid(telegramMeUser.captured(0).size()), start, startToken;
if (!params.isEmpty()) {
QRegularExpressionMatch startParams = QRegularExpression(qsl("(^|&)(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(params);
if (startParams.hasMatch()) {
start = startParams.captured(2);
startToken = startParams.captured(3);
}
}
App::openUserByName(telegramMeUser.captured(1), start == qsl("startgroup"), startToken);
} else if (telegramMeGroup.hasMatch()) {
App::joinGroupByHash(telegramMeGroup.captured(1));
} else if (telegramMeStickers.hasMatch()) {
@@ -763,6 +781,13 @@ void HashtagLink::onClick(Qt::MouseButton button) const {
}
}
void BotCommandLink::onClick(Qt::MouseButton button) const {
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
App::insertBotCommand(_cmd);
// App::sendBotCommand(_cmd);
}
}
class TextPainter {
public:
@@ -1088,9 +1113,33 @@ public:
if (_yTo >= 0 && _y + _yDelta >= _yTo) return false;
if (_y + _yDelta + _fontHeight <= _yFrom) return true;
uint16 trimmedLineEnd = _lineEnd;
for (; trimmedLineEnd > _lineStart; --trimmedLineEnd) {
QChar ch = _t->_text.at(trimmedLineEnd - 1);
if ((ch != QChar::Space || trimmedLineEnd == _lineStart + 1) && ch != QChar::LineFeed) {
break;
}
}
ITextBlock *_endBlock = (_endBlockIter == _end) ? 0 : (*_endBlockIter);
bool elidedLine = _elideLast && _endBlock && (_y + _lineHeight >= _yTo);
int blockIndex = _lineStartBlock;
ITextBlock *currentBlock = _t->_blocks[blockIndex];
ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
int32 delta = (currentBlock->from() < _lineStart ? qMin(_lineStart - currentBlock->from(), 2) : 0);
_localFrom = _lineStart - delta;
int32 lineEnd = (_endBlock && _endBlock->from() < trimmedLineEnd && !elidedLine) ? qMin(uint16(trimmedLineEnd + 2), _blockEnd(_t, _endBlockIter, _end)) : trimmedLineEnd;
QString lineText = _t->_text.mid(_localFrom, lineEnd - _localFrom);
int32 lineStart = delta, lineLength = trimmedLineEnd - _lineStart;
if (elidedLine) {
initParagraphBidi();
prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
}
QFixed x = _x;
if (_align & Qt::AlignHCenter) {
x += (_wLeft / 2).toInt();
@@ -1138,34 +1187,9 @@ public:
}
}
/* // lpadding is counted to _wLeft
for (; _lineStart < _lineEnd; ++_lineStart) {
if (_t->_text.at(_lineStart) != QChar::Space) {
break;
}
}/**/
for (; _lineEnd > _lineStart; --_lineEnd) {
QChar ch = _t->_text.at(_lineEnd - 1);
if ((ch != QChar::Space || _lineEnd == _lineStart + 1) && ch != QChar::LineFeed) {
break;
}
}/**/
if (_lineEnd == _lineStart && !elidedLine) return true;
if (trimmedLineEnd == _lineStart && !elidedLine) return true;
initParagraphBidi(); // if was not inited
int blockIndex = _lineStartBlock;
ITextBlock *currentBlock = _t->_blocks[blockIndex];
ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0;
int32 delta = (currentBlock->from() < _lineStart ? qMin(_lineStart - currentBlock->from(), 2) : 0);
_localFrom = _lineStart - delta;
int32 lineEnd = (_endBlock && _endBlock->from() < _lineEnd && !elidedLine) ? qMin(uint16(_lineEnd + 2), _blockEnd(_t, _endBlockIter, _end)) : _lineEnd;
QString lineText = _t->_text.mid(_localFrom, lineEnd - _localFrom);
int32 lineStart = delta, lineLength = _lineEnd - _lineStart;
if (elidedLine) prepareElidedLine(lineText, lineStart, lineLength, _endBlock);
if (!elidedLine) initParagraphBidi(); // if was not inited
_f = _t->_font;
QStackTextEngine engine(lineText, _f->f);
@@ -1202,7 +1226,7 @@ public:
}
if (si.analysis.flags == QScriptAnalysis::Object) {
if (_type == TextBlockEmoji || _type == TextBlockSkip) {
si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= _lineEnd) ? 0 : currentBlock->f_rpadding());
si.width = currentBlock->f_width() + (nextBlock == _endBlock && (!nextBlock || nextBlock->from() >= trimmedLineEnd) ? 0 : currentBlock->f_rpadding());
}
}
}
@@ -1250,8 +1274,8 @@ public:
*_getSymbolAfter = false;
*_getSymbolUpon = false;
} else {
*_getSymbol = (_lineEnd > _lineStart) ? (_lineEnd - 1) : _lineStart;
*_getSymbolAfter = (_lineEnd > _lineStart) ? true : false;
*_getSymbol = (trimmedLineEnd > _lineStart) ? (trimmedLineEnd - 1) : _lineStart;
*_getSymbolAfter = (trimmedLineEnd > _lineStart) ? true : false;
*_getSymbolUpon = false;
}
return false;
@@ -4071,7 +4095,9 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) {
LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp!
LinkRanges lnkRanges;
bool withHashtags = (flags & TextParseHashtags), withMentions = (flags & TextParseMentions);
bool withHashtags = (flags & TextParseHashtags);
bool withMentions = (flags & TextParseMentions);
bool withBotCommands = (flags & TextParseBotCommands);
initLinkSets();
int32 len = text.size(), nextCmd = rich ? 0 : len;
@@ -4088,6 +4114,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
LinkRange link;
int32 domainOffset = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX,
@@ -4097,7 +4124,9 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
hashtagOffset = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX,
hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX,
mentionOffset = mMention.hasMatch() ? mMention.capturedStart() : INT_MAX,
mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX;
mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX,
botCommandOffset = mBotCommand.hasMatch() ? mBotCommand.capturedStart() : INT_MAX,
botCommandEnd = mBotCommand.hasMatch() ? mBotCommand.capturedEnd() : INT_MAX;
if (mHashtag.hasMatch()) {
if (!mHashtag.capturedRef(1).isEmpty()) {
++hashtagOffset;
@@ -4127,14 +4156,24 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
break;
}
}
if (!mMention.hasMatch() && !mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch()) break;
if (mBotCommand.hasMatch()) {
if (!mBotCommand.capturedRef(1).isEmpty()) {
++botCommandOffset;
}
if (!mBotCommand.capturedRef(3).isEmpty()) {
--botCommandEnd;
}
}
if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch() && !mBotCommand.hasMatch()) {
break;
}
if (explicitDomainOffset < domainOffset) {
domainOffset = explicitDomainOffset;
domainEnd = explicitDomainEnd;
mDomain = mExplicitDomain;
}
if (mentionOffset < hashtagOffset && mentionOffset < domainOffset) {
if (mentionOffset < hashtagOffset && mentionOffset < domainOffset && mentionOffset < botCommandOffset) {
if (mentionOffset > nextCmd) {
const QChar *after = textSkipCommand(start + nextCmd, start + len);
if (after > start + nextCmd && mentionOffset < (after - start)) {
@@ -4145,7 +4184,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
link.from = start + mentionOffset;
link.len = start + mentionEnd - link.from;
} else if (hashtagOffset < domainOffset) {
} else if (hashtagOffset < domainOffset && hashtagOffset < botCommandOffset) {
if (hashtagOffset > nextCmd) {
const QChar *after = textSkipCommand(start + nextCmd, start + len);
if (after > start + nextCmd && hashtagOffset < (after - start)) {
@@ -4156,6 +4195,17 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some
link.from = start + hashtagOffset;
link.len = start + hashtagEnd - link.from;
} else if (botCommandOffset < domainOffset) {
if (botCommandOffset > nextCmd) {
const QChar *after = textSkipCommand(start + nextCmd, start + len);
if (after > start + nextCmd && botCommandOffset < (after - start)) {
nextCmd = offset = matchOffset = after - start;
continue;
}
}
link.from = start + botCommandOffset;
link.len = start + botCommandEnd - link.from;
} else {
if (domainOffset > nextCmd) {
const QChar *after = textSkipCommand(start + nextCmd, start + len);

View File

@@ -31,11 +31,12 @@ enum {
TextParseRichText = 0x004,
TextParseMentions = 0x008,
TextParseHashtags = 0x010,
TextParseBotCommands = 0x020,
TextTwitterMentions = 0x020,
TextTwitterHashtags = 0x040,
TextInstagramMentions = 0x080,
TextInstagramHashtags = 0x100,
TextTwitterMentions = 0x040,
TextTwitterHashtags = 0x080,
TextInstagramMentions = 0x100,
TextInstagramHashtags = 0x200,
};
struct LinkRange {
@@ -385,6 +386,32 @@ private:
};
class BotCommandLink : public ITextLink {
public:
BotCommandLink(const QString &cmd) : _cmd(cmd) {
}
const QString &text() const {
return _cmd;
}
void onClick(Qt::MouseButton button) const;
const QString &readable() const {
return _cmd;
}
QString encoded() const {
return _cmd;
}
private:
QString _cmd;
};
static const QChar TextCommand(0x0010);
enum TextCommands {
TextCommandBold = 0x01,
@@ -408,8 +435,7 @@ struct TextParseOptions {
int32 maxh;
Qt::LayoutDirection dir;
};
extern const TextParseOptions _defaultOptions;
extern const TextParseOptions _textPlainOptions;
extern const TextParseOptions _defaultOptions, _textPlainOptions;
enum TextSelectType {
TextSelectLetters = 0x01,
@@ -512,6 +538,7 @@ const QSet<int32> &validTopDomains();
const QRegularExpression &reDomain();
const QRegularExpression &reMailName();
const QRegularExpression &reHashtag();
const QRegularExpression &reBotCommand();
// text style
const style::textStyle *textstyleCurrent();

View File

@@ -56,6 +56,12 @@ public:
void drawPixmapLeft(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) {
return drawPixmapLeft(p.x(), p.y(), outerw, pix, from);
}
void drawPixmapLeft(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) {
drawPixmap(QRect(rtl() ? (outerw - x - w) : x, y, w, h), pix, from);
}
void drawPixmapLeft(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) {
return drawPixmapLeft(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
}
void drawPixmapLeft(int x, int y, int outerw, const QPixmap &pix) {
drawPixmap(QPoint(rtl() ? (outerw - x - (pix.width() / pix.devicePixelRatio())) : x, y), pix);
}
@@ -68,6 +74,12 @@ public:
void drawPixmapRight(const QPoint &p, int outerw, const QPixmap &pix, const QRect &from) {
return drawPixmapRight(p.x(), p.y(), outerw, pix, from);
}
void drawPixmapRight(int x, int y, int w, int h, int outerw, const QPixmap &pix, const QRect &from) {
drawPixmap(QRect(rtl() ? x : (outerw - x - w), y, w, h), pix, from);
}
void drawPixmapRight(const QRect &r, int outerw, const QPixmap &pix, const QRect &from) {
return drawPixmapRight(r.x(), r.y(), r.width(), r.height(), outerw, pix, from);
}
void drawPixmapRight(int x, int y, int outerw, const QPixmap &pix) {
drawPixmap(QPoint(rtl() ? x : (outerw - x - (pix.width() / pix.devicePixelRatio())), y), pix);
}
@@ -86,12 +98,24 @@ public:
void drawSpriteLeft(const QPoint &p, int outerw, const style::sprite &sprite) {
return drawPixmapLeft(p, outerw, App::sprite(), sprite);
}
void drawSpriteLeft(int x, int y, int w, int h, int outerw, const style::sprite &sprite) {
return drawPixmapLeft(x, y, w, h, outerw, App::sprite(), sprite);
}
void drawSpriteLeft(const QRect &r, int outerw, const style::sprite &sprite) {
return drawPixmapLeft(r, outerw, App::sprite(), sprite);
}
void drawSpriteRight(int x, int y, int outerw, const style::sprite &sprite) {
return drawPixmapRight(x, y, outerw, App::sprite(), sprite);
}
void drawSpriteRight(const QPoint &p, int outerw, const style::sprite &sprite) {
return drawPixmapRight(p, outerw, App::sprite(), sprite);
}
void drawSpriteRight(int x, int y, int w, int h, int outerw, const style::sprite &sprite) {
return drawPixmapRight(x, y, w, h, outerw, App::sprite(), sprite);
}
void drawSpriteRight(const QRect &r, int outerw, const style::sprite &sprite) {
return drawPixmapRight(r, outerw, App::sprite(), sprite);
}
void drawSpriteCenter(const QRect &in, const style::sprite &sprite) {
return drawPixmap(QPoint(in.x() + (in.width() - sprite.pxWidth()) / 2, in.y() + (in.height() - sprite.pxHeight()) / 2), App::sprite(), sprite);
}

View File

@@ -41,14 +41,20 @@ TextParseOptions _textDlgOptions = {
1, // maxh
Qt::LayoutDirectionAuto, // lang-dependent
};
TextParseOptions _historyTextOptions = {
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
TextParseOptions _historyBotOptions = {
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
namespace {
TextParseOptions _historyTextOptions = {
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
0, // maxw
0, // maxh
Qt::LayoutDirectionAuto, // dir
};
TextParseOptions _historySrvOptions = {
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
0, // maxw
@@ -103,6 +109,14 @@ namespace {
inline const HistoryForwarded *toHistoryForwarded(const HistoryItem *item) {
return item ? item->toHistoryForwarded() : 0;
}
inline const TextParseOptions &itemTextParseOptions(HistoryItem *item) {
History *h = item->history();
UserData *f = item->from();
if ((!h->peer->chat && h->peer->asUser()->botInfo) || (!f->chat && f->asUser()->botInfo) || (h->peer->chat && h->peer->asChat()->botStatus >= 0)) {
return _historyBotOptions;
}
return _historyTextOptions;
}
}
void historyInit() {
@@ -149,7 +163,7 @@ void DialogRow::paint(QPainter &p, int32 w, bool act, bool sel) const {
rectForName.setLeft(rectForName.left() + st::dlgChatImgSkip);
}
HistoryItem *last = history->last;
HistoryItem *last = history->lastMsg;
if (!last) {
p.setFont(st::dlgHistFont->f);
p.setPen((act ? st::dlgActiveColor : st::dlgSystemColor)->p);
@@ -290,12 +304,16 @@ History::History(const PeerId &peerId) : width(0), height(0)
, peer(App::peer(peerId))
, oldLoaded(false)
, newLoaded(true)
, last(0)
, lastMsg(0)
, activeMsgId(0)
, draftToId(0)
, lastWidth(0)
, lastScrollTop(History::ScrollMax)
, mute(isNotifyMuted(peer->notify))
, lastKeyboardInited(false)
, lastKeyboardUsed(false)
, lastKeyboardId(0)
, lastKeyboardFrom(0)
, sendRequestId(0)
, textCachedFor(0)
, lastItemTextCache(st::dlgRichMinWidth)
@@ -371,7 +389,7 @@ bool DialogsList::del(const PeerId &peerId, DialogRow *replacedBy) {
}
void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) {
if (byName) {
if (sortMode == DialogsSortByName) {
DialogRow *mainRow = list.adjustByName(peer);
if (!mainRow) return;
@@ -400,7 +418,7 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN
for (PeerData::NameFirstChars::const_iterator i = toAdd.cbegin(), e = toAdd.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j == index.cend()) {
j = index.insert(*i, new DialogsList(byName));
j = index.insert(*i, new DialogsList(sortMode));
}
j.value()->addByName(history);
}
@@ -422,7 +440,7 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN
}
}
for (PeerData::NameFirstChars::const_iterator i = toRemove.cbegin(), e = toRemove.cend(); i != e; ++i) {
history->dialogs.remove(*i);
if (sortMode == DialogsSortByDate) history->dialogs.remove(*i);
DialogsIndex::iterator j = index.find(*i);
if (j != index.cend()) {
j.value()->del(peer->id, mainRow);
@@ -431,9 +449,13 @@ void DialogsIndexed::peerNameChanged(PeerData *peer, const PeerData::Names &oldN
for (PeerData::NameFirstChars::const_iterator i = toAdd.cbegin(), e = toAdd.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j == index.cend()) {
j = index.insert(*i, new DialogsList(byName));
j = index.insert(*i, new DialogsList(sortMode));
}
if (sortMode == DialogsSortByDate) {
history->dialogs.insert(*i, j.value()->addByPos(history));
} else {
j.value()->addToEnd(history);
}
history->dialogs.insert(*i, j.value()->addByPos(history));
}
}
}
@@ -460,6 +482,8 @@ void Histories::regTyping(History *history, UserData *user) {
uint64 ms = getms(true);
history->typing[user] = ms + 6000;
user->madeAction();
TypingHistories::const_iterator i = typing.find(history);
if (i == typing.cend()) {
typing.insert(history, ms);
@@ -527,7 +551,7 @@ HistoryItem *Histories::addToBack(const MTPmessage &msg, int msgState) {
if (!h.value()->loadedAtBottom()) {
HistoryItem *item = h.value()->addToHistory(msg);
if (item) {
h.value()->last = item;
h.value()->lastMsg = item;
if (msgState > 0) {
h.value()->newItemAdded(item);
}
@@ -578,6 +602,9 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPmessage &msg, boo
} else {
result = new HistoryMessage(this, block, msg.c_message());
}
if (msg.c_message().has_reply_markup()) {
App::feedReplyMarkup(msgId, msg.c_message().vreply_markup);
}
break;
case mtpc_messageService: {
@@ -754,7 +781,7 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
}
}
to->push_back(adding);
last = adding;
lastMsg = adding;
adding->y = to->height;
if (width) {
int32 dh = adding->resize(width);
@@ -777,12 +804,33 @@ HistoryItem *History::doAddToBack(HistoryBlock *to, bool newBlock, HistoryItem *
}
}
}
if (peer->chat && adding->from()->id) {
QList<UserData*> *lastAuthors = &(peer->asChat()->lastAuthors);
int prev = lastAuthors->indexOf(adding->from());
if (prev > 0) {
lastAuthors->removeAt(prev);
lastAuthors->push_front(adding->from());
if (adding->from()->id) {
if (peer->chat) {
QList<UserData*> *lastAuthors = &(peer->asChat()->lastAuthors);
int prev = lastAuthors->indexOf(adding->from());
if (prev > 0) {
lastAuthors->removeAt(prev);
}
if (prev) {
lastAuthors->push_front(adding->from());
}
}
if (adding->hasReplyMarkup()) {
if (peer->chat) {
peer->asChat()->markupSenders.insert(adding->from(), true);
}
if (App::replyMarkup(adding->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO) { // zero markup means replyKeyboardHide
if (lastKeyboardFrom == adding->from()->id || (!lastKeyboardInited && !peer->chat && !adding->out())) {
lastKeyboardInited = true;
lastKeyboardId = 0;
lastKeyboardFrom = 0;
}
} else {
lastKeyboardInited = true;
lastKeyboardId = adding->id;
lastKeyboardFrom = adding->from()->id;
lastKeyboardUsed = false;
}
}
}
return adding;
@@ -802,10 +850,7 @@ void History::newItemAdded(HistoryItem *item) {
App::checkImageCacheSize();
if (item->from()) {
unregTyping(item->from());
if (item->from()->onlineTill < 0) {
item->from()->onlineTill = -unixtime() - HiddenIsOnlineAfterMessage; // pseudo-online
if (App::main()) App::main()->peerUpdated(item->from());
}
item->from()->madeAction();
}
if (item->out()) {
if (unreadBar) unreadBar->destroy();
@@ -889,7 +934,43 @@ void History::addToFront(const QVector<MTPMessage> &slice) {
}
}
}
if (lastAuthors && item->from()->id && !lastAuthors->contains(item->from())) lastAuthors->push_back(item->from());
if (item->from()->id) {
if (lastAuthors) { // chats
if (!lastAuthors->contains(item->from())) {
lastAuthors->push_back(item->from());
}
if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // chats with bots
bool wasKeyboardHide = peer->asChat()->markupSenders.contains(item->from());
if (!wasKeyboardHide) {
peer->asChat()->markupSenders.insert(item->from(), true);
}
if (!(App::replyMarkup(item->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO)) {
if (!lastKeyboardInited) {
lastKeyboardInited = true;
if (wasKeyboardHide) {
lastKeyboardId = 0;
lastKeyboardFrom = 0;
} else {
lastKeyboardId = item->id;
lastKeyboardFrom = item->from()->id;
lastKeyboardUsed = false;
}
}
}
}
} else if (!lastKeyboardInited && item->hasReplyMarkup() && !item->out()) { // conversations with bots
lastKeyboardInited = true;
if (App::replyMarkup(item->id).flags & MTPDreplyKeyboardMarkup_flag_ZERO) {
lastKeyboardId = 0;
lastKeyboardFrom = 0;
} else {
lastKeyboardInited = true;
lastKeyboardId = item->id;
lastKeyboardFrom = item->from()->id;
lastKeyboardUsed = false;
}
}
}
}
if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer);
}
@@ -1138,9 +1219,9 @@ void History::fixLastMessage(bool wasAtBottom) {
wasAtBottom = false;
}
if (wasAtBottom) {
last = back()->back();
lastMsg = back()->back();
} else {
last = 0;
lastMsg = 0;
if (App::main()) {
App::main()->checkPeerHistory(peer);
}
@@ -1156,12 +1237,12 @@ void History::loadAround(MsgId msgId) {
if (!item || !item->block()) {
clear(true);
}
newLoaded = last && !last->detached();
newLoaded = lastMsg && !lastMsg->detached();
} else {
if (!loadedAtBottom()) {
clear(true);
}
newLoaded = isEmpty() || (last && !last->detached());
newLoaded = isEmpty() || (lastMsg && !lastMsg->detached());
}
}
}
@@ -1245,13 +1326,19 @@ void History::clear(bool leaveItems) {
}
Parent::clear();
setMsgCount(0);
if (!leaveItems) {
if (leaveItems) {
lastKeyboardInited = false;
} else {
setUnreadCount(0);
last = 0;
lastMsg = 0;
}
height = 0;
oldLoaded = false;
if (peer->chat) peer->asChat()->lastAuthors.clear();
if (peer->chat) {
peer->asChat()->lastAuthors.clear();
peer->asChat()->markupSenders.clear();
}
if (leaveItems && App::main()) App::main()->historyCleared(this);
}
History::Parent::iterator History::erase(History::Parent::iterator i) {
@@ -1473,7 +1560,7 @@ void HistoryItem::destroy() {
bool wasAtBottom = history()->loadedAtBottom();
_history->removeNotification(this);
detach();
if (history()->last == this) {
if (history()->lastMsg == this) {
history()->fixLastMessage(wasAtBottom);
}
HistoryMedia *m = getMedia(true);
@@ -1549,7 +1636,7 @@ HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, const QString &caption, Histo
, _caption(st::minPhotoSize)
, openl(new PhotoLink(data)) {
if (!caption.isEmpty()) {
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(parent));
}
init();
}
@@ -1807,14 +1894,7 @@ void HistoryPhoto::draw(QPainter &p, const HistoryItem *parent, bool selected, i
height -= st::webPagePhotoSkip + _caption.countHeight(width);
}
} else {
QPixmap **cors = App::corners(selected ? InSelectedShadowCorners : InShadowCorners);
int32 cw = cors[0]->width() / cIntRetinaFactor(), ch = cors[0]->height() / cIntRetinaFactor();
style::color shadow(selected ? st::msgInSelectShadow : st::msgInShadow);
p.fillRect(cw, _height, width - 2 * cw, st::msgShadow, shadow->b);
p.fillRect(0, _height - ch, cw, st::msgShadow, shadow->b);
p.fillRect(width - cw, _height - ch, cw, st::msgShadow, shadow->b);
p.drawPixmap(0, _height - ch + st::msgShadow, *cors[2]);
p.drawPixmap(width - cw, _height - ch + st::msgShadow, *cors[3]);
App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
}
data->full->load(false, false);
bool full = data->full->loaded();
@@ -1956,7 +2036,7 @@ HistoryVideo::HistoryVideo(const MTPDvideo &video, const QString &caption, Histo
, _uplDone(0)
{
if (!caption.isEmpty()) {
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);
_caption.setText(st::msgFont, caption + textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(parent));
}
_size = formatDurationAndSizeText(data->duration, data->size);
@@ -2358,8 +2438,7 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i
width = _maxw;
}
bool mp3 = (data->mime == QLatin1String("audio/mp3"));
if (!data->loader && !mp3 && data->status != FileFailed && !already && !hasdata && data->size < AudioVoiceMsgInMemory) {
if (!data->loader && data->status != FileFailed && !already && !hasdata && data->size < AudioVoiceMsgInMemory) {
data->save(QString());
}
@@ -2399,18 +2478,47 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i
}
AudioData *playing = 0;
VoiceMessageState playingState = VoiceMessageStopped;
AudioPlayerState playingState = AudioPlayerStopped;
int64 playingPosition = 0, playingDuration = 0;
if (!mp3 && audioVoice()) {
audioVoice()->currentState(&playing, &playingState, &playingPosition, &playingDuration);
int32 playingFrequency = 0;
if (audioPlayer()) {
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
}
QRect img;
if (!mp3 && (already || hasdata)) {
bool showPause = (playing == data) && (playingState == VoiceMessagePlaying || playingState == VoiceMessageResuming || playingState == VoiceMessageStarting);
QString statusText;
if (data->status == FileFailed) {
statusText = lang(lng_attach_failed);
img = out ? st::mediaAudioOutImg : st::mediaAudioInImg;
} else if (data->status == FileUploading) {
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
_uplDone = data->uploadOffset;
_uplTextCache = formatDownloadText(_uplDone, data->size);
}
statusText = _uplTextCache;
img = out ? st::mediaAudioOutImg : st::mediaAudioInImg;
} else if (already || hasdata) {
bool showPause = false;
if (playing == data && playingState != AudioPlayerStopped && playingState != AudioPlayerStoppedAtStart) {
statusText = formatDurationText(playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency)) + qsl(" / ") + formatDurationText(playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
} else {
statusText = formatDurationText(data->duration);
}
img = out ? (showPause ? st::mediaPauseOutImg : st::mediaPlayOutImg) : (showPause ? st::mediaPauseInImg : st::mediaPlayInImg);
} else {
if (data->loader) {
if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) {
_dldDone = data->loader->currentOffset();
_dldTextCache = formatDownloadText(_dldDone, data->size);
}
statusText = _dldTextCache;
} else {
statusText = _size;
}
img = out ? st::mediaAudioOutImg : st::mediaAudioInImg;
}
p.drawPixmap(QPoint(st::mediaPadding.left(), skipy + st::mediaPadding.top()), App::sprite(), img);
if (selected) {
App::roundRect(p, st::mediaPadding.left(), skipy + st::mediaPadding.top(), st::mediaThumbSize, st::mediaThumbSize, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
@@ -2425,37 +2533,8 @@ void HistoryAudio::draw(QPainter &p, const HistoryItem *parent, bool selected, i
p.setPen(st::black->c);
p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaNameTop + st::mediaFont->ascent, lang(lng_media_audio));
QString statusText;
style::color status(selected ? (out ? st::mediaOutSelectColor : st::mediaInSelectColor) : (out ? st::mediaOutColor : st::mediaInColor));
p.setPen(status->p);
if (!mp3 && (already || hasdata)) {
if (playing == data && playingState != VoiceMessageStopped) {
statusText = formatDurationText(playingPosition / AudioVoiceMsgFrequency) + qsl(" / ") + formatDurationText(playingDuration / AudioVoiceMsgFrequency);
} else {
statusText = formatDurationText(data->duration);
}
} else {
if (data->loader) {
if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) {
_dldDone = data->loader->currentOffset();
_dldTextCache = formatDownloadText(_dldDone, data->size);
}
statusText = _dldTextCache;
} else {
if (data->status == FileFailed) {
statusText = lang(lng_attach_failed);
} else if (data->status == FileUploading) {
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
_uplDone = data->uploadOffset;
_uplTextCache = formatDownloadText(_uplDone, data->size);
}
statusText = _uplTextCache;
} else {
statusText = _size;
}
}
}
int32 texty = skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->height;
p.drawText(tleft, texty + st::mediaFont->ascent, statusText);
if (parent->isMediaUnread()) {
@@ -2498,6 +2577,15 @@ void HistoryAudio::unregItem(HistoryItem *item) {
App::unregAudioItem(data, item);
}
void HistoryAudio::updateFrom(const MTPMessageMedia &media) {
if (media.type() == mtpc_messageMediaAudio) {
App::feedAudio(media.c_messageMediaAudio().vaudio, data);
if (!data->data.isEmpty()) {
Local::writeAudio(mediaKey(mtpToLocationType(mtpc_inputAudioFileLocation), data->dc, data->id), data->data);
}
}
}
const QString HistoryAudio::inDialogsText() const {
return lang(lng_in_dlg_audio);
}
@@ -2616,8 +2704,8 @@ HistoryDocument::HistoryDocument(DocumentData *document) : HistoryMedia()
void HistoryDocument::initDimensions(const HistoryItem *parent) {
if (parent == animated.msg) {
_maxw = animated.w;
_minh = animated.h;
_maxw = animated.w / cIntRetinaFactor();
_minh = animated.h / cIntRetinaFactor();
} else {
_maxw = st::mediaMaxWidth;
int32 tleft = st::mediaPadding.left() + st::mediaThumbSize + st::mediaPadding.right();
@@ -2650,23 +2738,16 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected
bool out = parent->out(), hovered, pressed;
if (parent == animated.msg) {
int32 pw = animated.w, ph = animated.h;
int32 pw = animated.w / cIntRetinaFactor(), ph = animated.h / cIntRetinaFactor();
if (width < pw) {
pw = width;
ph = (pw == w) ? _height : (pw * animated.h / animated.w);
if (ph < 1) ph = 1;
}
QPixmap **cors = App::corners(selected ? InSelectedShadowCorners : InShadowCorners);
int32 cw = cors[0]->width() / cIntRetinaFactor(), ch = cors[0]->height() / cIntRetinaFactor();
style::color shadow(selected ? st::msgInSelectShadow : st::msgInShadow);
p.fillRect(cw, ph, pw - 2 * cw, st::msgShadow, shadow->b);
p.fillRect(0, ph - ch, cw, st::msgShadow, shadow->b);
p.fillRect(pw - cw, ph - ch, cw, st::msgShadow, shadow->b);
p.drawPixmap(0, ph - ch + st::msgShadow, *cors[2]);
p.drawPixmap(pw - cw, ph - ch + st::msgShadow, *cors[3]);
App::roundShadow(p, 0, 0, pw, ph, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
p.drawPixmap(0, 0, animated.current(pw, ph, true));
p.drawPixmap(0, 0, animated.current(pw * cIntRetinaFactor(), ph * cIntRetinaFactor(), true));
if (selected) {
App::roundRect(p, 0, 0, pw, ph, textstyleCurrent()->selectOverlay, SelectedOverlayCorners);
}
@@ -2757,24 +2838,22 @@ void HistoryDocument::draw(QPainter &p, const HistoryItem *parent, bool selected
style::color status(selected ? (out ? st::mediaOutSelectColor : st::mediaInSelectColor) : (out ? st::mediaOutColor : st::mediaInColor));
p.setPen(status->p);
if (data->loader) {
if (data->status == FileFailed) {
statusText = lang(lng_attach_failed);
} else if (data->status == FileUploading) {
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
_uplDone = data->uploadOffset;
_uplTextCache = formatDownloadText(_uplDone, data->size);
}
statusText = _uplTextCache;
} else if (data->loader) {
if (_dldTextCache.isEmpty() || _dldDone != data->loader->currentOffset()) {
_dldDone = data->loader->currentOffset();
_dldTextCache = formatDownloadText(_dldDone, data->size);
}
statusText = _dldTextCache;
} else {
if (data->status == FileFailed) {
statusText = lang(lng_attach_failed);
} else if (data->status == FileUploading) {
if (_uplTextCache.isEmpty() || _uplDone != data->uploadOffset) {
_uplDone = data->uploadOffset;
_uplTextCache = formatDownloadText(_uplDone, data->size);
}
statusText = _uplTextCache;
} else {
statusText = _size;
}
statusText = _size;
}
p.drawText(tleft, skipy + st::mediaPadding.top() + st::mediaThumbSize - st::mediaDetailsShift - st::mediaFont->descent, statusText);
@@ -2820,9 +2899,9 @@ int32 HistoryDocument::resize(int32 width, bool dontRecountText, const HistoryIt
if (w > st::maxMediaSize) {
w = st::maxMediaSize;
}
_height = animated.h;
if (animated.w > w) {
_height = (w * _height / animated.w);
_height = animated.h / cIntRetinaFactor();
if (animated.w / cIntRetinaFactor() > w) {
_height = (w * _height / (animated.w / cIntRetinaFactor()));
if (_height <= 0) _height = 1;
}
} else {
@@ -2985,7 +3064,7 @@ void HistorySticker::initDimensions(const HistoryItem *parent) {
_maxw = qMax(pixw, int16(st::minPhotoSize));
_minh = qMax(pixh, int16(st::minPhotoSize));
if (const HistoryReply *reply = toHistoryReply(parent)) {
_maxw += reply->replyToWidth();
_maxw += st::msgReplyPadding.left() + reply->replyToWidth();
}
_height = _minh;
w = qMin(lastw, _maxw);
@@ -2999,7 +3078,7 @@ void HistorySticker::draw(QPainter &p, const HistoryItem *parent, bool selected,
int32 usew = _maxw, usex = 0;
const HistoryReply *reply = toHistoryReply(parent);
if (reply) {
usew -= reply->replyToWidth();
usew -= st::msgReplyPadding.left() + reply->replyToWidth();
if (parent->out()) {
usex = width - usew;
}
@@ -3062,8 +3141,8 @@ void HistorySticker::draw(QPainter &p, const HistoryItem *parent, bool selected,
}
if (reply) {
int32 rw = width - usew, rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
int32 rx = parent->out() ? 0 : usew, ry = _height - rh;
int32 rw = width - usew - st::msgReplyPadding.left(), rh = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
int32 rx = parent->out() ? 0 : (usew + st::msgReplyPadding.left()), ry = _height - rh;
App::roundRect(p, rx, ry, rw, rh, selected ? App::msgServiceSelectBg() : App::msgServiceBg(), selected ? ServiceSelectedCorners : ServiceCorners);
@@ -3088,7 +3167,9 @@ void HistorySticker::unregItem(HistoryItem *item) {
void HistorySticker::updateFrom(const MTPMessageMedia &media) {
if (media.type() == mtpc_messageMediaDocument) {
App::feedDocument(media.c_messageMediaDocument().vdocument, data);
if (App::main()) App::main()->incrementSticker(data);
if (!data->data.isEmpty()) {
Local::writeStickerImage(mediaKey(mtpToLocationType(mtpc_inputDocumentFileLocation), data->dc, data->id), data->data);
}
}
}
@@ -3422,11 +3503,13 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) {
}
QString title(data->title.isEmpty() ? data->author : data->title);
if (!title.isEmpty()) {
_title.setText(st::webPageTitleFont, textClean(title), _webpageTitleOptions);
title = textClean(title);
if (!_asArticle && !data->photo && data->description.isEmpty()) title += textcmdSkipBlock(parent->timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y());
_title.setText(st::webPageTitleFont, title, _webpageTitleOptions);
if (_asArticle) {
_maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize));
} else {
_maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth() + (data->photo ? parent->timeWidth(true) : 0)));
_maxw = qMax(_maxw, int32(st::webPageLeft + _title.maxWidth()));
_minh += qMin(_title.minHeight(), 2 * st::webPageTitleFont->height);
}
}
@@ -4305,14 +4388,7 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte
}
height -= skipy + st::mediaPadding.bottom();
} else {
QPixmap **cors = App::corners(selected ? InSelectedShadowCorners : InShadowCorners);
int32 cw = cors[0]->width() / cIntRetinaFactor(), ch = cors[0]->height() / cIntRetinaFactor();
style::color shadow(selected ? st::msgInSelectShadow : st::msgInShadow);
p.fillRect(cw, _height, width - 2 * cw, st::msgShadow, shadow->b);
p.fillRect(0, _height - ch, cw, st::msgShadow, shadow->b);
p.fillRect(width - cw, _height - ch, cw, st::msgShadow, shadow->b);
p.drawPixmap(0, _height - ch + st::msgShadow, *cors[2]);
p.drawPixmap(width - cw, _height - ch + st::msgShadow, *cors[3]);
App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInSelectShadow : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
}
data->load();
@@ -4669,9 +4745,9 @@ void HistoryMessage::initMediaFromDocument(DocumentData *doc) {
void HistoryMessage::initDimensions(const QString &text) {
if (!_media || !text.isEmpty()) { // !justMedia()
if (_media && _media->isDisplayed()) {
_text.setText(st::msgFont, text, _historyTextOptions);
_text.setText(st::msgFont, text, itemTextParseOptions(this));
} else {
_text.setText(st::msgFont, text + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);
_text.setText(st::msgFont, text + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this));
}
}
}
@@ -4690,14 +4766,14 @@ void HistoryMessage::initDimensions(const HistoryItem *parent) {
if (_media->isDisplayed() && _text.hasSkipBlock()) {
QString was = HistoryMessage::selectedText(FullItemSel);
if (!was.isEmpty()) {
_text.setText(st::msgFont, was, _historyTextOptions); // without date skip
_text.setText(st::msgFont, was, itemTextParseOptions(this)); // without date skip
_textWidth = 0;
_textHeight = 0;
}
} else if (!_media->isDisplayed() && !_text.hasSkipBlock()) {
QString was = HistoryMessage::selectedText(FullItemSel);
if (!was.isEmpty()) {
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); // without date skip
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this)); // without date skip
_textWidth = 0;
_textHeight = 0;
}
@@ -4754,14 +4830,14 @@ void HistoryMessage::setMedia(const MTPmessageMedia &media) {
if (_media && _media->isDisplayed() && !mediaWasDisplayed) {
QString was = HistoryMessage::selectedText(FullItemSel);
if (!was.isEmpty()) {
_text.setText(st::msgFont, was, _historyTextOptions); // without date skip
_text.setText(st::msgFont, was, itemTextParseOptions(this)); // without date skip
_textWidth = 0;
_textHeight = 0;
}
} else if (mediaWasDisplayed && (!_media || !_media->isDisplayed())) {
QString was = HistoryMessage::selectedText(FullItemSel);
if (!was.isEmpty()) {
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); // without date skip
_text.setText(st::msgFont, was + textcmdSkipBlock(timeWidth(true), st::msgDateFont->height - st::msgDateDelta.y()), itemTextParseOptions(this)); // without date skip
_textWidth = 0;
_textHeight = 0;
}
@@ -5070,6 +5146,9 @@ HistoryMessage::~HistoryMessage() {
_media->unregItem(this);
delete _media;
}
if (_flags & MTPDmessage::flag_reply_markup) {
App::clearReplyMarkup(id);
}
}
HistoryForwarded::HistoryForwarded(History *history, HistoryBlock *block, const MTPDmessage &msg) : HistoryMessage(history, block, msg.vid.v, msg.vflags.v, ::date(msg.vdate), msg.vfrom_id.v, textClean(qs(msg.vmessage)), msg.vmedia)

View File

@@ -30,11 +30,10 @@ static const uint32 FullItemSel = 0xFFFFFFFF;
typedef QMap<int32, HistoryItem*> SelectedItemSet;
extern TextParseOptions _textNameOptions, _textDlgOptions;
extern TextParseOptions _textNameOptions, _textDlgOptions, _historyTextOptions, _historyBotOptions;
#include "structs.h"
struct History;
struct Histories : public QHash<PeerId, History*>, public Animated {
typedef QHash<PeerId, History*> Parent;
@@ -199,7 +198,7 @@ struct History : public QList<HistoryBlock*> {
PeerData *peer;
bool oldLoaded, newLoaded;
HistoryItem *last;
HistoryItem *lastMsg;
MsgId activeMsgId;
typedef QList<HistoryItem*> NotifyQueue;
@@ -239,8 +238,8 @@ struct History : public QList<HistoryBlock*> {
}
}
}
if (last == old) {
last = item;
if (lastMsg == old) {
lastMsg = item;
}
// showFrom can't be detached
}
@@ -252,6 +251,10 @@ struct History : public QList<HistoryBlock*> {
int32 lastWidth, lastScrollTop;
bool mute;
bool lastKeyboardInited, lastKeyboardUsed;
MsgId lastKeyboardId;
PeerId lastKeyboardFrom;
mtpRequestId sendRequestId;
// for dialog drawing
@@ -285,8 +288,14 @@ struct History : public QList<HistoryBlock*> {
static const int32 ScrollMax = INT_MAX;
};
enum DialogsSortMode {
DialogsSortByDate,
DialogsSortByName,
DialogsSortByAdd
};
struct DialogsList {
DialogsList(bool sortByName) : begin(&last), end(&last), byName(sortByName), count(0), current(&last) {
DialogsList(DialogsSortMode sortMode) : begin(&last), end(&last), sortMode(sortMode), count(0), current(&last) {
}
void adjustCurrent(int32 y, int32 h) const {
@@ -324,10 +333,10 @@ struct DialogsList {
end->pos++;
if (begin == end) {
begin = current = result;
if (!byName && updatePos) history->posInDialogs = 0;
if (sortMode == DialogsSortByDate && updatePos) history->posInDialogs = 0;
} else {
end->prev->next = result;
if (!byName && updatePos) history->posInDialogs = end->prev->history->posInDialogs + 1;
if (sortMode == DialogsSortByDate && updatePos) history->posInDialogs = end->prev->history->posInDialogs + 1;
}
rowByPeer.insert(history->peer->id, result);
++count;
@@ -335,7 +344,7 @@ struct DialogsList {
}
void bringToTop(DialogRow *row, bool updatePos = true) {
if (!byName && updatePos && row != begin) {
if (sortMode == DialogsSortByDate && updatePos && row != begin) {
row->history->posInDialogs = begin->history->posInDialogs - 1;
}
insertBefore(row, begin);
@@ -390,7 +399,7 @@ struct DialogsList {
}
DialogRow *adjustByName(const PeerData *peer) {
if (!byName) return 0;
if (sortMode != DialogsSortByName) return 0;
RowByPeer::iterator i = rowByPeer.find(peer->id);
if (i == rowByPeer.cend()) return 0;
@@ -409,7 +418,7 @@ struct DialogsList {
}
DialogRow *addByName(History *history) {
if (!byName) return 0;
if (sortMode != DialogsSortByName) return 0;
DialogRow *row = addToEnd(history), *change = row;
const QString &peerName(history->peer->name);
@@ -426,7 +435,7 @@ struct DialogsList {
}
void adjustByPos(DialogRow *row) {
if (byName) return;
if (sortMode != DialogsSortByDate) return;
DialogRow *change = row;
while (change->prev && change->prev->history->posInDialogs > row->history->posInDialogs) {
@@ -441,7 +450,7 @@ struct DialogsList {
}
DialogRow *addByPos(History *history) {
if (byName) return 0;
if (sortMode != DialogsSortByDate) return 0;
DialogRow *row = addToEnd(history, false);
adjustByPos(row);
@@ -476,7 +485,7 @@ struct DialogsList {
DialogRow last;
DialogRow *begin, *end;
bool byName;
DialogsSortMode sortMode;
int32 count;
typedef QHash<PeerId, DialogRow*> RowByPeer;
@@ -486,7 +495,7 @@ struct DialogsList {
};
struct DialogsIndexed {
DialogsIndexed(bool sortByName) : byName(sortByName), list(byName) {
DialogsIndexed(DialogsSortMode sortMode) : sortMode(sortMode), list(sortMode) {
}
History::DialogLinks addToEnd(History *history) {
@@ -500,7 +509,7 @@ struct DialogsIndexed {
for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j == index.cend()) {
j = index.insert(*i, new DialogsList(byName));
j = index.insert(*i, new DialogsList(sortMode));
}
result.insert(*i, j.value()->addToEnd(history));
}
@@ -518,7 +527,7 @@ struct DialogsIndexed {
for (PeerData::NameFirstChars::const_iterator i = history->peer->chars.cbegin(), e = history->peer->chars.cend(); i != e; ++i) {
DialogsIndex::iterator j = index.find(*i);
if (j == index.cend()) {
j = index.insert(*i, new DialogsList(byName));
j = index.insert(*i, new DialogsList(sortMode));
}
j.value()->addByName(history);
}
@@ -557,7 +566,7 @@ struct DialogsIndexed {
void clear();
bool byName;
DialogsSortMode sortMode;
DialogsList list;
typedef QMap<QChar, DialogsList*> DialogsIndex;
DialogsIndex index;
@@ -679,6 +688,9 @@ public:
void markMediaRead() {
_flags &= ~MTPDmessage_flag_media_unread;
}
bool hasReplyMarkup() const {
return _flags & MTPDmessage::flag_reply_markup;
}
virtual bool needCheck() const {
return true;
}
@@ -910,6 +922,7 @@ private:
};
QString formatSizeText(qint64 size);
QString formatDurationText(qint64 duration);
class HistoryVideo : public HistoryMedia {
public:
@@ -971,9 +984,15 @@ public:
}
HistoryMedia *clone() const;
AudioData *audio() {
return data;
}
void regItem(HistoryItem *item);
void unregItem(HistoryItem *item);
void updateFrom(const MTPMessageMedia &media);
private:
AudioData *data;
TextLinkPtr _openl, _savel, _cancell;

File diff suppressed because it is too large Load Diff

View File

@@ -79,6 +79,8 @@ public:
void itemRemoved(HistoryItem *item);
void itemReplaced(HistoryItem *oldItem, HistoryItem *newItem);
void updateBotInfo(bool recount = true);
~HistoryList();
public slots:
@@ -116,6 +118,12 @@ private:
void applyDragSelection();
History *hist;
int32 ySkip;
BotInfo *botInfo;
int32 botDescWidth, botDescHeight;
QRect botDescRect;
HistoryWidget *historyWidget;
ScrollArea *scrollArea;
int32 currentBlock, currentItem;
@@ -172,6 +180,7 @@ public:
void insertFromMimeData(const QMimeData *source);
void focusInEvent(QFocusEvent *e);
void setMaxHeight(int32 maxHeight);
public slots:
@@ -185,6 +194,72 @@ signals:
private:
HistoryWidget *history;
int32 _maxHeight;
};
class BotKeyboard : public QWidget {
Q_OBJECT
public:
BotKeyboard();
void paintEvent(QPaintEvent *e);
void resizeEvent(QResizeEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void leaveEvent(QEvent *e);
bool updateMarkup(HistoryItem *last);
bool hasMarkup() const;
bool hoverStep(float64 ms);
void resizeToWidth(int32 width, int32 maxOuterHeight);
bool maximizeSize() const;
bool singleUse() const;
MsgId forMsgId() const {
return _wasForMsgId;
}
public slots:
void showCommandTip();
void updateSelected();
private:
void updateStyle(int32 w = -1);
void clearSelection();
MsgId _wasForMsgId;
int32 _height, _maxOuterHeight;
bool _maximizeSize, _singleUse;
QTimer _cmdTipTimer;
QPoint _lastMousePos;
struct Button {
Button(const QString &str = QString()) : cmd(str), text(1), cwidth(0), hover(0), full(true) {
}
QRect rect;
QString cmd;
Text text;
int32 cwidth;
float64 hover;
bool full;
};
int32 _sel, _down;
QList<QList<Button> > _btns;
typedef QMap<int32, uint64> Animations;
Animations _animations;
Animation _hoverAnim;
const style::botKeyboardButton *_st;
};
class HistoryHider : public QWidget, public Animated {
@@ -250,7 +325,7 @@ private:
};
class HistoryWidget : public QWidget, public RPCSender, public Animated {
class HistoryWidget : public TWidget, public RPCSender {
Q_OBJECT
public:
@@ -273,6 +348,8 @@ public:
void leaveEvent(QEvent *e);
void dropEvent(QDropEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void leaveToChildEvent(QEvent *e);
void contextMenuEvent(QContextMenuEvent *e);
void updateTopBarSelection();
@@ -292,11 +369,11 @@ public:
void newUnreadMsg(History *history, HistoryItem *item);
void historyToDown(History *history);
void historyWasRead(bool force = true);
void historyCleared(History *history);
QRect historyRect() const;
void updateTyping(bool typing = true);
// void updateStickerPan();
void updateRecentStickers();
void stickersInstalled(uint64 setId);
void typingDone(const MTPBool &result, mtpRequestId req);
@@ -317,7 +394,6 @@ public:
void updateOnlineDisplay(int32 x, int32 w);
void updateOnlineDisplayTimer();
// mtpRequestId onForward(const PeerId &peer, SelectedItemSet toForward);
void onShareContact(const PeerId &peer, UserData *contact);
void onSendPaths(const PeerId &peer);
@@ -330,7 +406,7 @@ public:
int32 lastScrollTop() const;
void animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTopBarCache, bool back = false);
bool animStep(float64 ms);
bool showStep(float64 ms);
void animStop();
QPoint clampMousePosition(QPoint point);
@@ -367,6 +443,15 @@ public:
void updatePreview();
void previewCancel();
bool recordStep(float64 ms);
bool recordingStep(float64 ms);
void stopRecording(bool send);
void onListEscapePressed();
void sendBotCommand(const QString &cmd, MsgId replyTo);
void insertBotCommand(const QString &cmd);
~HistoryWidget();
signals:
@@ -387,27 +472,33 @@ public slots:
void onPreviewTimeout();
void peerUpdated(PeerData *data);
void onPeerLoaded(PeerData *data);
void onFullPeerUpdated(PeerData *data);
void cancelTyping();
void onPhotoUploaded(MsgId msgId, const MTPInputFile &file);
void onDocumentUploaded(MsgId msgId, const MTPInputFile &file);
void onThumbDocumentUploaded(MsgId msgId, const MTPInputFile &file, const MTPInputFile &thumb);
void onAudioUploaded(MsgId msgId, const MTPInputFile &file);
void onDocumentProgress(MsgId msgId);
void onAudioProgress(MsgId msgId);
void onDocumentFailed(MsgId msgId);
void onAudioFailed(MsgId msgId);
void onListScroll();
void onHistoryToEnd();
void onSend(bool ctrlShiftEnter = false, MsgId replyTo = -1);
void onBotStart();
void onPhotoSelect();
void onDocumentSelect();
void onPhotoDrop(QDropEvent *e);
void onDocumentDrop(QDropEvent *e);
void onKbToggle(bool manual = true);
void onPhotoReady();
void onSendConfirmed();
void onSendCancelled();
@@ -443,6 +534,10 @@ public slots:
void updateStickers();
void onRecordError();
void onRecordDone(QByteArray result, qint32 samples);
void onRecordUpdate(qint16 level, qint32 samples);
private:
MsgId _replyToId;
@@ -451,7 +546,11 @@ private:
int32 _replyToNameVersion;
IconedButton _replyForwardPreviewCancel;
void updateReplyToName();
void drawFieldBackground(QPainter &p);
void drawField(Painter &p);
void drawRecordButton(Painter &p);
void drawRecording(Painter &p);
void updateField();
QString _previewLinks;
WebPageData *_previewData;
@@ -473,6 +572,8 @@ private:
void addMessagesToFront(const QVector<MTPMessage> &messages);
void addMessagesToBack(const QVector<MTPMessage> &messages);
void updateBotKeyboard();
void stickersGot(const MTPmessages_AllStickers &stickers);
bool stickersFailed(const RPCError &error);
@@ -500,19 +601,30 @@ private:
ScrollArea _scroll;
HistoryList *_list;
History *hist;
bool _histInited; // initial updateListSize() called
bool _histInited, _histNeedUpdate; // initial updateListSize() called
IconedButton _toHistoryEnd;
MentionsDropdown _attachMention;
FlatButton _send;
IconedButton _attachDocument, _attachPhoto, _attachEmoji;
FlatButton _send, _botStart;
IconedButton _attachDocument, _attachPhoto, _attachEmoji, _kbShow, _kbHide;
MessageField _field;
Animation _recordAnim, _recordingAnim;
bool _recording, _inRecord, _inField;
anim::ivalue a_recordingLevel;
int32 _recordingSamples;
anim::fvalue a_recordOver, a_recordDown;
anim::cvalue a_recordCancel;
int32 _recordCancelWidth;
bool _kbShown, _kbWasHidden;
HistoryItem *_kbReplyTo;
ScrollArea _kbScroll;
BotKeyboard _keyboard;
Dropdown _attachType;
EmojiPan _emojiPan;
// StickerPan _stickerPan;
DragState _attachDrag;
DragArea _attachDragDocument, _attachDragPhoto;
@@ -532,6 +644,7 @@ private:
bool hiderOffered;
Animation _showAnim;
QPixmap _animCache, _bgAnimCache, _animTopBarCache, _bgAnimTopBarCache;
anim::ivalue a_coord, a_bgCoord;
anim::fvalue a_alpha, a_bgAlpha;

View File

@@ -202,7 +202,7 @@ void IntroCode::codeSubmitDone(const MTPauth_Authorization &result) {
stopCheck();
code.setDisabled(false);
const MTPDauth_authorization &d(result.c_auth_authorization());
if (d.vuser.type() != mtpc_userSelf) { // wtf?
if (d.vuser.type() != mtpc_user || !(d.vuser.c_user().vflags.v & MTPDuser_flag_self)) { // wtf?
showError(lang(lng_server_error));
return;
}

View File

@@ -181,7 +181,7 @@ void IntroPwdCheck::pwdSubmitDone(bool recover, const MTPauth_Authorization &res
_pwdField.setDisabled(false);
_codeField.setDisabled(false);
const MTPDauth_authorization &d(result.c_auth_authorization());
if (d.vuser.type() != mtpc_userSelf) { // wtf?
if (d.vuser.type() != mtpc_user || !(d.vuser.c_user().vflags.v & MTPDuser_flag_self)) { // wtf?
showError(lang(lng_server_error));
return;
}

View File

@@ -216,7 +216,7 @@ void IntroSignup::nameSubmitDone(const MTPauth_Authorization &result) {
first.setDisabled(false);
last.setDisabled(false);
const MTPDauth_authorization &d(result.c_auth_authorization());
if (d.vuser.type() != mtpc_userSelf) { // wtf?
if (d.vuser.type() != mtpc_user || !(d.vuser.c_user().vflags.v & MTPDuser_flag_self)) { // wtf?
showError(lang(lng_server_error));
return;
}

View File

@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "Jetzt versuchen";
"lng_status_service_notifications" = "Servicemeldungen";
"lng_status_bot" = "Bot";
"lng_status_bot_reads_all" = "hat Zugriff auf Nachrichten";
"lng_status_bot_not_reads_all" = "hat keinen Zugriff auf Nachrichten";
"lng_status_offline" = "vor langer Zeit gesehen";
"lng_status_recently" = "kürzlich gesehen";
"lng_status_last_week" = "innerhalb einer Woche gesehen";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "Gruppe nicht verfügbar";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Info";
"lng_profile_settings_section" = "Einstellungen";
"lng_profile_bot_settings" = "Einstellungen";
"lng_profile_bot_help" = "Hilfe";
"lng_profile_participants_section" = "Teilnehmer";
"lng_profile_info" = "Kontaktinfo";
"lng_profile_group_info" = "Gruppeninfo";
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_clear_history" = "Chatverlauf löschen";
"lng_profile_send_message" = "Nachricht senden";
"lng_profile_share_contact" = "Kontakt teilen";
"lng_profile_invite_to_group" = "In eine Gruppe einladen";
"lng_profile_delete_contact" = "Löschen";
"lng_profile_set_group_photo" = "Bild festlegen";
"lng_profile_add_participant" = "Neues Mitglied";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "Senden";
"lng_message_ph" = "Schreibe deine Nachricht..";
"lng_record_cancel" = "Zum Abbrechen rausbewegen";
"lng_empty_history" = "";
"lng_willbe_history" = "Wähle einen Chat aus, um zu schreiben";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "Ich";
"lng_bot_description" = "Was kann dieser Bot?";
"lng_bot_start" = "Starten";
"lng_bot_choose_group" = "Gruppe auswählen";
"lng_bot_no_groups" = "Du hast keine Gruppen";
"lng_bot_groups_not_found" = "Keine Gruppen gefunden";
"lng_bot_sure_invite" = "Den Bot zu «{group}» hinzufügen?";
"lng_bot_already_in_group" = "Der Bot ist schon in dieser Gruppe.";
"lng_typing" = "tippt";
"lng_user_typing" = "{user} tippt";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}";
"lng_new_version_minor" = "— Fehlerbehebungen und Softwareoptimierungen";
"lng_new_version_text" = "— Installiere und teile benutzerdefinierte Sticker-Pakete\n— Neue Emoji und ein Panel für Sticker";
"lng_new_version_text" = "Diese neue Version enthält Unterstützung für die neue Bot API, welche für alle kostenlos verfügbar ist. Kannst du programmieren? Erstelle deine eigenen Bots für Spiele, Dienste oder Integrationen.\n\nMehr dazu unter {blog_link}";
"lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen";

View File

@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "Intentar ahora";
"lng_status_service_notifications" = "servicio de notificaciones";
"lng_status_bot" = "bot";
"lng_status_bot_reads_all" = "tiene acceso a los mensajes";
"lng_status_bot_not_reads_all" = "no tiene acceso a los mensajes";
"lng_status_offline" = "última vez hace mucho tiempo";
"lng_status_recently" = "última vez recientemente";
"lng_status_last_week" = "última vez hace unos días";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "El grupo es inaccesible";
"lng_topbar_info" = "Información";
"lng_profile_about_section" = "Acerca de";
"lng_profile_settings_section" = "Ajustes";
"lng_profile_bot_settings" = "Ajustes";
"lng_profile_bot_help" = "Ayuda";
"lng_profile_participants_section" = "Miembros";
"lng_profile_info" = "Información";
"lng_profile_group_info" = "Información";
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_clear_history" = "Borrar historial";
"lng_profile_send_message" = "Enviar mensaje";
"lng_profile_share_contact" = "Compartir contacto";
"lng_profile_invite_to_group" = "Añadir al grupo";
"lng_profile_delete_contact" = "Eliminar";
"lng_profile_set_group_photo" = "Poner foto";
"lng_profile_add_participant" = "Añadir miembro";
@@ -396,7 +403,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_group_invite_link" = "Invitación";
"lng_group_invite_create" = "Crear un enlace de invitación";
"lng_group_invite_about" = "Los usuarios de Telegram podrán unirse\na tu grupo a través de este enlace.";
"lng_group_invite_create_new" = "Anular enlace de invitación";
"lng_group_invite_create_new" = "Anular enlace";
"lng_group_invite_about_new" = "El enlace previo será desactivado\ny generaremos uno nuevo para ti.";
"lng_group_invite_copied" = "Enlace de invitación copiado al portapapeles.";
"lng_group_invite_no_room" = "No puedes unirte a este grupo porque\nhay muchos miembros en él.";
@@ -420,13 +427,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_media_video" = "Vídeo";
"lng_media_audio" = "Mensaje de voz";
"lng_emoji_category0" = "Usados frecuentemente";
"lng_emoji_category1" = "Gente";
"lng_emoji_category0" = "Usados con frecuencia";
"lng_emoji_category1" = "Personas";
"lng_emoji_category2" = "Naturaleza";
"lng_emoji_category3" = "Comida y bebidas";
"lng_emoji_category3" = "Comida y bebida";
"lng_emoji_category4" = "Celebración";
"lng_emoji_category5" = "Actividad";
"lng_emoji_category6" = "Viajes y lugares";
"lng_emoji_category6" = "Viajes y destinos";
"lng_emoji_category7" = "Objetos y símbolos";
"lng_switch_stickers" = "Stickers";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "Enviar";
"lng_message_ph" = "Escribir un mensaje...";
"lng_record_cancel" = "Suelta fuera de aquí para cancelar";
"lng_empty_history" = "";
"lng_willbe_history" = "Por favor, elige un chat para comenzar a conversar";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "Tú";
"lng_bot_description" = "¿Qué puede hacer este bot?";
"lng_bot_start" = "Empezar";
"lng_bot_choose_group" = "Elegir grupo";
"lng_bot_no_groups" = "No tienes grupos";
"lng_bot_groups_not_found" = "No se encontraron grupos";
"lng_bot_sure_invite" = "¿Añadir el bot a «{group}»?";
"lng_bot_already_in_group" = "El bot ya es un miembro del grupo.";
"lng_typing" = "escribiendo";
"lng_user_typing" = "{user} está escribiendo";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop fue actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}";
"lng_new_version_minor" = "— Corrección de errores y otras mejoras menores";
"lng_new_version_text" = "— Añadido el soporte para packs de stickers\n— Nuevas pestañas de emojis y stickers";
"lng_new_version_text" = "Esta nueva versión incluye el soporte para bots, usando la nueva API para bots, gratis para todos. Si eres un ingeniero, crea tus propios bots para juegos, servicios o integraciones .\n\nConoce más en {blog_link}";
"lng_menu_insert_unicode" = "Insertar caracteres de control Unicode";

View File

@@ -28,7 +28,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_open_from_tray" = "Apri Telegram";
"lng_minimize_to_tray" = "Minimizza";
"lng_quit_from_tray" = "Chiudi Telegram";
"lng_tray_icon_text" = "Telegram è ancora aperto qui,\npuoi cambiare questo nelle impostazioni.\n\nSe l'icona scompare dalla barra tray,\npuoi riportarla indietro dalle icone nascoste.";
"lng_tray_icon_text" = "Telegram è ancora aperto qui,\npuoi cambiare questo nelle impostazioni.\n\nSe l'icona scompare dall'area di notifica,\npuoi riportarla indietro dalle icone nascoste.";
"lng_month1" = "Gennaio";
"lng_month2" = "Febbraio";
@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "Prova ora";
"lng_status_service_notifications" = "notifiche di servizio";
"lng_status_bot" = "bot";
"lng_status_bot_reads_all" = "ha accesso ai messaggi";
"lng_status_bot_not_reads_all" = "non ha accesso ai messaggi";
"lng_status_offline" = "ultimo accesso molto tempo fa";
"lng_status_recently" = "ultimo accesso di recente";
"lng_status_last_week" = "ultimo accesso entro una settimana";
@@ -196,10 +199,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_settings_update_ready" = "La nuova versione è pronta";
"lng_settings_update_now" = "Riavvia ora";
"lng_settings_update_fail" = "Ricerca aggiornamenti fallita :(";
"lng_settings_workmode_tray" = "Mostra icona nella barra tray";
"lng_settings_workmode_tray" = "Mostra icona nell'area di notifica";
"lng_settings_workmode_window" = "Mostra icona nella barra applicazioni";
"lng_settings_auto_start" = "Avvia Telegram all'avvio del sistema";
"lng_settings_start_min" = "Avvia minimizzato";
"lng_settings_start_min" = "Avvia ridotto a icona";
"lng_settings_add_sendto" = "Inserisci Telegram nel menu «Invia a»";
"lng_settings_scale_label" = "Ridimensiona interfaccia";
"lng_settings_scale_auto" = "Auto ({cur})";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "Gruppo non accessibile";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Info";
"lng_profile_settings_section" = "Impostazioni";
"lng_profile_bot_settings" = "Impostazioni";
"lng_profile_bot_help" = "Aiuto";
"lng_profile_participants_section" = "Membri";
"lng_profile_info" = "Info contatto";
"lng_profile_group_info" = "Nome gruppo";
@@ -343,12 +349,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_clear_history" = "Cancella la cronologia";
"lng_profile_send_message" = "Invia messaggio";
"lng_profile_share_contact" = "Condividi contatto";
"lng_profile_invite_to_group" = "Aggiungi a un gruppo";
"lng_profile_delete_contact" = "Elimina";
"lng_profile_set_group_photo" = "Imposta foto";
"lng_profile_add_participant" = "Aggiungi membro";
"lng_profile_delete_and_exit" = "Esci";
"lng_profile_kick" = "Rimuovi";
"lng_profile_sure_kick" = "Espellere {user} dal gruppo?";
"lng_profile_sure_kick" = "Rimuovere {user} dal gruppo?";
"lng_profile_loading" = "Caricamento..";
"lng_profile_shared_media" = "Media condivisi";
"lng_profile_no_media" = "Nessun media in questa chat.";
@@ -383,7 +390,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_action_user_left" = "{from} ha lasciato il gruppo";
"lng_action_user_joined" = "{from} si è unito al gruppo";
"lng_action_user_joined_by_link" = "{from} si è unito al gruppo tramite link di invito";
"lng_action_user_registered" = "{from} ha iniziato a usare Telegram";
"lng_action_user_registered" = "{from} si è unito a Telegram";
"lng_action_removed_photo" = "{from} ha rimosso la foto del gruppo";
"lng_action_changed_photo" = "{from} ha cambiato la foto del gruppo";
"lng_action_changed_title" = "{from} ha cambiato il nome del gruppo in «{title}»";
@@ -434,7 +441,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_custom_stickers" = "Sticker personalizzati";
"lng_stickers_remove_pack" = "Rimuovere «{sticker_pack}»?";
"lng_stickers_add_pack" = "Aggiungi {count:_not_used_|# sticker|# stickers}";
"lng_stickers_add_pack" = "Aggiungi {count:_not_used_|# sticker|# sticker}";
"lng_stickers_share_pack" = "Condividi sticker";
"lng_stickers_not_found" = "Pacchetto di sticker non trovato.";
"lng_stickers_copied" = "Link degli sticker copiato negli appunti.";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "Invia";
"lng_message_ph" = "Scrivi un messaggio..";
"lng_record_cancel" = "Rilascia fuori da qui per annullare";
"lng_empty_history" = "";
"lng_willbe_history" = "Seleziona una chat per iniziare a messaggiare";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "Tu";
"lng_bot_description" = "Cosa può fare questo bot?";
"lng_bot_start" = "Avvia";
"lng_bot_choose_group" = "Scegli gruppo";
"lng_bot_no_groups" = "Non hai gruppi";
"lng_bot_groups_not_found" = "Nessun gruppo trovato";
"lng_bot_sure_invite" = "Aggiungere il bot a «{group}»?";
"lng_bot_already_in_group" = "Questo bot è già membro del gruppo.";
"lng_typing" = "sta scrivendo";
"lng_user_typing" = "{user} sta scrivendo";
@@ -491,7 +507,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_context_save_video" = "Salva video come..";
"lng_context_open_audio" = "Apri audio";
"lng_context_save_audio" = "Salva audio come..";
"lng_context_pack_info" = "Info pacchetto";
"lng_context_pack_info" = "Mostra sticker";
"lng_context_open_file" = "Apri file";
"lng_context_save_file" = "Salva file come..";
"lng_context_forward_file" = "Inoltra file";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli update è disponibile qui:\n{link}";
"lng_new_version_minor" = "— Bug fix e altri miglioramenti minori";
"lng_new_version_text" = "— Aggiunto supporto ai pacchetti di sticker\n— Nuove emoji e pannello sticker";
"lng_new_version_text" = "Questa nuova versione include il support per i bot usando la nuova API per i bot. Se sei un ingegnere, crea i tuoi bot per giochi, servizi o integrazioni. Scopri di più su {blog_link}";
"lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode";

View File

@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "다시 시도";
"lng_status_service_notifications" = "서비스 알림";
"lng_status_bot" = "bot";
"lng_status_bot_reads_all" = "메시지 접근 권한이 있습니다.";
"lng_status_bot_not_reads_all" = "메시지 접근 권한이 없습니다.";
"lng_status_offline" = "마지막으로 접속한 지 오래됨";
"lng_status_recently" = "최근에 접속";
"lng_status_last_week" = "일주일 이내 마지막으로 접속";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "그룹에 접근할 수 없습니다.";
"lng_topbar_info" = "정보";
"lng_profile_about_section" = "취소";
"lng_profile_settings_section" = "환경설정";
"lng_profile_bot_settings" = "환경설정";
"lng_profile_bot_help" = "도움말";
"lng_profile_participants_section" = "사용자";
"lng_profile_info" = "연락처 정보";
"lng_profile_group_info" = "그룹 정보";
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_clear_history" = "히스토리 초기화";
"lng_profile_send_message" = "메세지 전송";
"lng_profile_share_contact" = "연락처 공유";
"lng_profile_invite_to_group" = "그룹에 추가";
"lng_profile_delete_contact" = "삭제";
"lng_profile_set_group_photo" = "사진 설정";
"lng_profile_add_participant" = "대화상대 추가";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "보내기";
"lng_message_ph" = "메시지 쓰기";
"lng_record_cancel" = "Release outside this field to cancel";
"lng_empty_history" = "";
"lng_willbe_history" = "대화하실 방을 선택해주세요.";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "회원님";
"lng_bot_description" = "봇이 할 수 있는 일은 무엇일까요?";
"lng_bot_start" = "Start";
"lng_bot_choose_group" = "그룹 선택";
"lng_bot_no_groups" = "그룹이 존재하지 않습니다.";
"lng_bot_groups_not_found" = "그룹을 찾을 수 없습니다.";
"lng_bot_sure_invite" = "<<{group}>>에 봇을 추가 하시겠습니까?";
"lng_bot_already_in_group" = "봇이 이미 그룹의 멤버입니다.";
"lng_typing" = "입력중";
"lng_user_typing" = "{user}님이 입력중입니다.";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}";
"lng_new_version_minor" = "— 버그 수정 및 일부 기능 향상";
"lng_new_version_text" = "— 스티커 팩 기능\n— 신규 이모티콘 및 스티커 패널";
"lng_new_version_text" = "This new version includes support for bots using the new bot API, free for everyone. If you're an engineer, create your own bots for games, services or integrations.\n\nLearn more at {blog_link}";
"lng_menu_insert_unicode" = "유니코드 문자를 입력하세요.";

View File

@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "Probeer nu";
"lng_status_service_notifications" = "servicemeldingen";
"lng_status_bot" = "bot";
"lng_status_bot_reads_all" = "toegang tot berichten";
"lng_status_bot_not_reads_all" = "geen toegang tot berichten";
"lng_status_offline" = "lang geleden gezien";
"lng_status_recently" = "recent gezien";
"lng_status_last_week" = "afgelopen week gezien";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "Groep is ontoegankelijk";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Over";
"lng_profile_settings_section" = "Instellingen";
"lng_profile_bot_settings" = "Instellingen";
"lng_profile_bot_help" = "Help";
"lng_profile_participants_section" = "Deelnemers";
"lng_profile_info" = "Contactinformatie";
"lng_profile_group_info" = "Groepsinformatie";
@@ -343,6 +349,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_clear_history" = "Geschiedenis wissen";
"lng_profile_send_message" = "Bericht sturen";
"lng_profile_share_contact" = "Contact delen";
"lng_profile_invite_to_group" = "Groepslid maken";
"lng_profile_delete_contact" = "Verwijder";
"lng_profile_set_group_photo" = "Foto instellen";
"lng_profile_add_participant" = "Lid toevoegen";
@@ -396,7 +403,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_group_invite_link" = "Uitnodigingslink";
"lng_group_invite_create" = "Uitnodigingslink maken";
"lng_group_invite_about" = "Gebruikers kunnen aan je groep \ndeelnemen met deze link.";
"lng_group_invite_create_new" = "Uitnodigingslink intrekken";
"lng_group_invite_create_new" = "Intrekken";
"lng_group_invite_about_new" = "Je uitnodigingslink zal inactief worden\neen nieuwe link zal worden gegenereerd.";
"lng_group_invite_copied" = "Link gekopieerd naar klembord.";
"lng_group_invite_no_room" = "Sorry, deze groep is al vol.";
@@ -425,7 +432,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_emoji_category2" = "Natuur";
"lng_emoji_category3" = "Eten & drinken";
"lng_emoji_category4" = "Feestelijkheid";
"lng_emoji_category5" = "Actviteit";
"lng_emoji_category5" = "Activiteit";
"lng_emoji_category6" = "Reizen & plekken";
"lng_emoji_category7" = "Objecten & symbolen";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "Stuur";
"lng_message_ph" = "Bericht schrijven";
"lng_record_cancel" = "Annuleren: uit het vak loslaten";
"lng_empty_history" = "";
"lng_willbe_history" = "Kies een chat om te beginnen";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "Jij";
"lng_bot_description" = "Wat kan deze bot? ";
"lng_bot_start" = "Begin";
"lng_bot_choose_group" = "Groep kiezen";
"lng_bot_no_groups" = "Je hebt geen groepen";
"lng_bot_groups_not_found" = "Geen groepen gevonden";
"lng_bot_sure_invite" = "De bot toevoegen aan «{group}»?";
"lng_bot_already_in_group" = "De bot neemt al deel aan de groep.";
"lng_typing" = "aan het typen";
"lng_user_typing" = "{user} is aan het typen";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}";
"lng_new_version_minor" = "— Probleemoplossing en andere kleine verbeteringen";
"lng_new_version_text" = "— Ondersteuning voor stickerbundels toegevoegd\n— Nieuw emoji- en stickerspaneel";
"lng_new_version_text" = "Deze versie heeft ondersteuning voor de nieuwe bot-API, gratis voor iedereen. Handig met programmeren? Maak dan je eigen bots voor spelletjes, diensten en integraties. \n\nMeer weten? kijk op: {blog_link}";
"lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen";

View File

@@ -69,6 +69,9 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_reconnecting_try_now" = "Tentar agora";
"lng_status_service_notifications" = "notificações de serviço";
"lng_status_bot" = "bot";
"lng_status_bot_reads_all" = "tem acesso as mensagens";
"lng_status_bot_not_reads_all" = "não tem acesso as mensagens";
"lng_status_offline" = "visto há muito tempo";
"lng_status_recently" = "visto recentemente";
"lng_status_last_week" = "visto há uma semana";
@@ -333,7 +336,10 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_chat_unaccessible" = "Grupo inacessível";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Sobre";
"lng_profile_settings_section" = "Configurações";
"lng_profile_bot_settings" = "Configurações";
"lng_profile_bot_help" = "Ajuda";
"lng_profile_participants_section" = "Membros";
"lng_profile_info" = "Informação de contato";
"lng_profile_group_info" = "Informação do grupo";
@@ -342,7 +348,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_profile_enable_notifications" = "Notificações";
"lng_profile_clear_history" = "Limpar histórico";
"lng_profile_send_message" = "Enviar Mensagem";
"lng_profile_share_contact" = "Compartilhar Contato";
"lng_profile_share_contact" = "Compartilhar";
"lng_profile_invite_to_group" = "Adicionar ao Grupo";
"lng_profile_delete_contact" = "Apagar";
"lng_profile_set_group_photo" = "Definir Foto";
"lng_profile_add_participant" = "Adicionar Membro";
@@ -450,10 +457,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_send_button" = "Enviar";
"lng_message_ph" = "Escrever a mensagem..";
"lng_record_cancel" = "Solte fora desse campo para cancelar";
"lng_empty_history" = "";
"lng_willbe_history" = "Selecione um chat para começar a conversar";
"lng_message_with_from" = "[c]{from}:[/c] {message}";
"lng_from_you" = "Você";
"lng_bot_description" = "O que esse bot pode fazer?";
"lng_bot_start" = "Iniciar";
"lng_bot_choose_group" = "Escolher Grupo";
"lng_bot_no_groups" = "Você não possui grupos";
"lng_bot_groups_not_found" = "Nenhum grupo encontrado";
"lng_bot_sure_invite" = "Adicionar bot ao «{group}»?";
"lng_bot_already_in_group" = "O bot já é um membro do grupo.";
"lng_typing" = "escrevendo";
"lng_user_typing" = "{user} está escrevendo";
@@ -584,7 +600,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}";
"lng_new_version_minor" = "— Resolução de bugs e outras menores melhorias";
"lng_new_version_text" = "— Suporte para os pacotes de sticker adicionado\n— Novo painel de sticker e emoji";
"lng_new_version_text" = "Essa nova versão inclui suporte para bots usando a nova API de bots. Se você for um engenheiro, crie seus próprios bots para jogos, serviços ou integrações.\n\nLeia mais em {blog_link}";
"lng_menu_insert_unicode" = "Inserir caractere de controle Unicode";

View File

@@ -25,7 +25,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "gui/filedialog.h"
BackgroundWidget::BackgroundWidget(QWidget *parent, LayeredWidget *w) : QWidget(parent), w(w), _hidden(0),
aBackground(0), aBackgroundFunc(anim::easeOutCirc), hiding(false), shadow(st::boxShadow) {
aBackground(0), aBackgroundFunc(anim::easeOutCirc), hiding(false), shadow(st::boxShadow) {
w->setParent(this);
setGeometry(0, 0, App::wnd()->width(), App::wnd()->height());
aBackground.start(1);
@@ -54,7 +54,7 @@ void BackgroundWidget::paintEvent(QPaintEvent *e) {
p.fillRect(rect(), st::layerBG->b);
p.setOpacity(aBackground.current());
shadow.paint(p, w->boxRect());
shadow.paint(p, w->boxRect(), st::boxShadowShift);
}
void BackgroundWidget::keyPressEvent(QKeyEvent *e) {

View File

@@ -37,6 +37,7 @@ void LocalImageLoaderPrivate::prepareImages() {
QByteArray data;
PeerId peer;
uint64 id, thumbId = 0;
int32 duration = 0;
QString thumbExt = "jpg";
ToPrepareMediaType type;
bool animated = false;
@@ -53,6 +54,7 @@ void LocalImageLoaderPrivate::prepareImages() {
peer = list.front().peer;
id = list.front().id;
type = list.front().type;
duration = list.front().duration;
ctrlShiftEnter = list.front().ctrlShiftEnter;
replyTo = list.front().replyTo;
}
@@ -85,29 +87,34 @@ void LocalImageLoaderPrivate::prepareImages() {
filename = info.fileName();
filesize = info.size();
} else if (!data.isEmpty()) {
img = App::readImage(data, 0, true, &animated);
if (type == ToPrepareAuto) {
if (!img.isNull() && data.size() < MaxUploadPhotoSize) {
type = ToPreparePhoto;
} else if (data.size() < MaxUploadDocumentSize) {
type = ToPrepareDocument;
} else {
img = QImage();
if (type != ToPrepareAudio) {
img = App::readImage(data, 0, true, &animated);
if (type == ToPrepareAuto) {
if (!img.isNull() && data.size() < MaxUploadPhotoSize) {
type = ToPreparePhoto;
} else if (data.size() < MaxUploadDocumentSize) {
type = ToPrepareDocument;
} else {
img = QImage();
}
}
}
MimeType mimeType = mimeTypeForData(data);
if (type == ToPrepareDocument) {
if (type == ToPrepareDocument || type == ToPrepareAudio) {
mime = mimeType.name();
}
if (mime == "image/jpeg") {
filename = filedialogDefaultName(qsl("image"), qsl(".jpg"), QString(), true);
} else if (type == ToPrepareAudio) {
filename = filedialogDefaultName(qsl("audio"), qsl(".ogg"), QString(), true);
mime = "audio/ogg";
} else {
QString ext;
QStringList patterns = mimeType.globPatterns();
if (!patterns.isEmpty()) {
ext = patterns.front().replace('*', QString());
}
filename = filedialogDefaultName(qsl("doc"), ext, QString(), true);
filename = filedialogDefaultName((type == ToPrepareAudio) ? qsl("audio") : qsl("doc"), ext, QString(), true);
}
filesize = data.size();
}
@@ -136,7 +143,7 @@ void LocalImageLoaderPrivate::prepareImages() {
}
}
if ((img.isNull() && (type != ToPrepareDocument || !filesize)) || type == ToPrepareAuto || (img.isNull() && file.isEmpty() && data.isEmpty())) { // if could not decide what type
if ((img.isNull() && ((type != ToPrepareDocument && type != ToPrepareAudio) || !filesize)) || type == ToPrepareAuto || (img.isNull() && file.isEmpty() && data.isEmpty())) { // if could not decide what type
{
QMutexLocker lock(loader->toPrepareMutex());
ToPrepareMedias &list(loader->toPrepareMedias());
@@ -155,6 +162,7 @@ void LocalImageLoaderPrivate::prepareImages() {
MTPPhotoSize thumb(MTP_photoSizeEmpty(MTP_string("")));
MTPPhoto photo(MTP_photoEmpty(MTP_long(0)));
MTPDocument document(MTP_documentEmpty(MTP_long(0)));
MTPAudio audio(MTP_audioEmpty(MTP_long(0)));
QByteArray jpeg;
if (type == ToPreparePhoto) {
@@ -210,11 +218,13 @@ void LocalImageLoaderPrivate::prepareImages() {
if (type == ToPrepareDocument) {
document = MTP_document(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(filesize), thumb, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
} else if (type == ToPrepareAudio) {
audio = MTP_audio(MTP_long(id), MTP_long(0), MTP_int(user), MTP_int(unixtime()), MTP_int(duration), MTP_string(mime), MTP_int(filesize), MTP_int(MTP::maindc()));
}
{
QMutexLocker lock(loader->readyMutex());
loader->readyList().push_back(ReadyLocalMedia(type, file, filename, filesize, data, id, thumbId, thumbExt, peer, photo, photoThumbs, document, jpeg, ctrlShiftEnter, replyTo));
loader->readyList().push_back(ReadyLocalMedia(type, file, filename, filesize, data, id, thumbId, thumbExt, peer, photo, audio, photoThumbs, document, jpeg, ctrlShiftEnter, replyTo));
}
{
@@ -267,6 +277,22 @@ PhotoId LocalImageLoader::append(const QByteArray &img, const PeerId &peer, MsgI
return result;
}
AudioId LocalImageLoader::append(const QByteArray &audio, int32 duration, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t) {
AudioId result = 0;
{
QMutexLocker lock(toPrepareMutex());
toPrepare.push_back(ToPrepareMedia(audio, duration, peer, t, false, replyTo));
result = toPrepare.back().id;
}
if (!thread) {
thread = new QThread();
priv = new LocalImageLoaderPrivate(MTP::authedId(), this, thread);
thread->start();
}
emit needToPrepare();
return result;
}
PhotoId LocalImageLoader::append(const QImage &img, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t, bool ctrlShiftEnter) {
PhotoId result = 0;
{

View File

@@ -20,16 +20,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
enum ToPrepareMediaType {
ToPrepareAuto,
ToPreparePhoto,
ToPrepareAudio,
ToPrepareVideo,
ToPrepareDocument,
};
struct ToPrepareMedia {
ToPrepareMedia(const QString &file, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), file(file), peer(peer), type(t), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
ToPrepareMedia(const QString &file, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), file(file), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
}
ToPrepareMedia(const QImage &img, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), img(img), peer(peer), type(t), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
ToPrepareMedia(const QImage &img, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), img(img), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
}
ToPrepareMedia(const QByteArray &data, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
ToPrepareMedia(const QByteArray &data, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
}
ToPrepareMedia(const QByteArray &data, int32 duration, const PeerId &peer, ToPrepareMediaType t, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), duration(duration), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
}
PhotoId id;
QString file;
@@ -37,6 +40,7 @@ struct ToPrepareMedia {
QByteArray data;
PeerId peer;
ToPrepareMediaType type;
int32 duration;
bool ctrlShiftEnter;
MsgId replyTo;
};
@@ -44,8 +48,8 @@ typedef QList<ToPrepareMedia> ToPrepareMedias;
typedef QMap<int32, QByteArray> LocalFileParts;
struct ReadyLocalMedia {
ReadyLocalMedia(ToPrepareMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, bool ctrlShiftEnter, MsgId replyTo) :
replyTo(replyTo), type(type), file(file), filename(filename), filesize(filesize), data(data), thumbExt(thumbExt), id(id), thumbId(thumbId), peer(peer), photo(photo), document(document), photoThumbs(photoThumbs), ctrlShiftEnter(ctrlShiftEnter) {
ReadyLocalMedia(ToPrepareMediaType type, const QString &file, const QString &filename, int32 filesize, const QByteArray &data, const uint64 &id, const uint64 &thumbId, const QString &thumbExt, const PeerId &peer, const MTPPhoto &photo, const MTPAudio &audio, const PreparedPhotoThumbs &photoThumbs, const MTPDocument &document, const QByteArray &jpeg, bool ctrlShiftEnter, MsgId replyTo) :
replyTo(replyTo), type(type), file(file), filename(filename), filesize(filesize), data(data), thumbExt(thumbExt), id(id), thumbId(thumbId), peer(peer), photo(photo), document(document), audio(audio), photoThumbs(photoThumbs), ctrlShiftEnter(ctrlShiftEnter) {
if (!jpeg.isEmpty()) {
int32 size = jpeg.size();
for (int32 i = 0, part = 0; i < size; i += UploadPartSize, ++part) {
@@ -66,6 +70,7 @@ struct ReadyLocalMedia {
MTPPhoto photo;
MTPDocument document;
MTPAudio audio;
PreparedPhotoThumbs photoThumbs;
LocalFileParts parts;
QByteArray jpeg_md5;
@@ -107,6 +112,7 @@ public:
LocalImageLoader(QObject *parent);
void append(const QStringList &files, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
PhotoId append(const QByteArray &img, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
AudioId append(const QByteArray &audio, int32 duration, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);
PhotoId append(const QImage &img, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t, bool ctrlShiftEnter = false);
PhotoId append(const QString &file, const PeerId &peer, MsgId replyTo, ToPrepareMediaType t);

View File

@@ -609,13 +609,22 @@ namespace {
mtpDcOptions *_dcOpts = 0;
bool _readSetting(quint32 blockId, QDataStream &stream, int version) {
switch (blockId) {
case dbiDcOption: {
case dbiDcOptionOld: {
quint32 dcId, port;
QString host, ip;
stream >> dcId >> host >> ip >> port;
if (!_checkStreamStatus(stream)) return false;
if (_dcOpts) _dcOpts->insert(dcId, mtpDcOption(dcId, host.toUtf8().constData(), ip.toUtf8().constData(), port));
if (_dcOpts) _dcOpts->insert(dcId, mtpDcOption(dcId, 0, ip.toUtf8().constData(), port));
} break;
case dbiDcOption: {
quint32 dcIdWithShift, flags, port;
QString ip;
stream >> dcIdWithShift >> flags >> ip >> port;
if (!_checkStreamStatus(stream)) return false;
if (_dcOpts) _dcOpts->insert(dcIdWithShift, mtpDcOption(dcIdWithShift % _mtp_internal::dcShift, flags, ip.toUtf8().constData(), port));
} break;
case dbiMaxGroupCount: {
@@ -1723,9 +1732,16 @@ namespace Local {
if (dcOpts.isEmpty()) {
const BuiltInDc *bdcs = builtInDcs();
for (int i = 0, l = builtInDcsCount(); i < l; ++i) {
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, "", bdcs[i].ip, bdcs[i].port));
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, 0, bdcs[i].ip, bdcs[i].port));
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port));
}
const BuiltInDc *bdcsipv6 = builtInDcsIPv6();
for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) {
int32 flags = MTPDdcOption_flag_ipv6, idWithShift = bdcsipv6[i].id + (flags * _mtp_internal::dcShift);
dcOpts.insert(idWithShift, mtpDcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port));
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port));
}
}
{
QWriteLocker lock(MTP::dcOptionsMutex());
@@ -1759,10 +1775,16 @@ namespace Local {
if (dcOpts.isEmpty()) {
const BuiltInDc *bdcs = builtInDcs();
for (int i = 0, l = builtInDcsCount(); i < l; ++i) {
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, "", bdcs[i].ip, bdcs[i].port));
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, 0, bdcs[i].ip, bdcs[i].port));
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port));
}
const BuiltInDc *bdcsipv6 = builtInDcsIPv6();
for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) {
dcOpts.insert(bdcsipv6[i].id + (MTPDdcOption_flag_ipv6 * _mtp_internal::dcShift), mtpDcOption(bdcsipv6[i].id, MTPDdcOption_flag_ipv6, bdcsipv6[i].ip, bdcsipv6[i].port));
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port));
}
QWriteLocker lock(MTP::dcOptionsMutex());
cSetDcOptions(dcOpts);
}
@@ -1770,7 +1792,7 @@ namespace Local {
quint32 size = 10 * (sizeof(quint32) + sizeof(qint32));
for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32);
size += _stringSize(QString::fromUtf8(i->host.data(), i->host.size())) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size()));
size += sizeof(quint32) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size()));
}
size += sizeof(quint32) + _stringSize(cLangFile());
@@ -1794,8 +1816,8 @@ namespace Local {
data.stream << quint32(dbiScale) << qint32(cConfigScale());
data.stream << quint32(dbiLang) << qint32(cLang());
for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
data.stream << quint32(dbiDcOption) << quint32(i->id);
data.stream << QString::fromUtf8(i->host.data(), i->host.size()) << QString::fromUtf8(i->ip.data(), i->ip.size());
data.stream << quint32(dbiDcOption) << quint32(i.key());
data.stream << quint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size());
data.stream << quint32(i->port);
}
data.stream << quint32(dbiLangFile) << cLangFile();
@@ -2266,7 +2288,7 @@ namespace Local {
}
}
EncryptedDescriptor data(size);
data.stream << quint32(cStickerSetsOrder().size()) << cStickersHash();
data.stream << quint32(cStickerSets().size()) << cStickersHash();
_writeStickerSet(data.stream, DefaultStickerSetId);
_writeStickerSet(data.stream, CustomStickerSetId);
for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) {
@@ -2287,7 +2309,7 @@ namespace Local {
_writeMap();
return;
}
StickerSets &sets(cRefStickerSets());
sets.clear();
cSetStickerSetsOrder(StickerSetsOrder());
@@ -2367,6 +2389,10 @@ namespace Local {
quint32 cnt;
QByteArray hash;
stickers.stream >> cnt >> hash;
if (stickers.version < 8019) {
hash.clear(); // bad data in old caches
cnt += 2; // try to read at least something
}
for (uint32 i = 0; i < cnt; ++i) {
quint64 setId = 0, setAccess = 0;
QString setTitle, setShortName;
@@ -2377,8 +2403,10 @@ namespace Local {
setTitle = lang(lng_stickers_default_set);
} else if (setId == CustomStickerSetId) {
setTitle = lang(lng_custom_stickers);
} else {
} else if (setId) {
order.push_back(setId);
} else {
continue;
}
StickerSet &set(sets.insert(setId, StickerSet(setId, setAccess, setTitle, setShortName)).value());
set.stickers.reserve(scnt);

View File

@@ -66,6 +66,7 @@ void debugLogWrite(const char *file, int32 line, const QString &v) {
{
QMutexLocker lock(&debugLogMutex);
if (!cDebug() || !debugLogStream) return;
logsInitDebug(); // maybe need to reopen new file
@@ -87,6 +88,7 @@ void tcpLogWrite(const QString &v) {
{
QMutexLocker lock(&debugLogMutex);
if (!cDebug() || !tcpLogStream) return;
logsInitDebug(); // maybe need to reopen new file
@@ -100,6 +102,7 @@ void mtpLogWrite(int32 dc, const QString &v) {
{
QMutexLocker lock(&debugLogMutex);
if (!cDebug() || !mtpLogStream) return;
logsInitDebug(); // maybe need to reopen new file
@@ -109,7 +112,7 @@ void mtpLogWrite(int32 dc, const QString &v) {
}
void logWrite(const QString &v) {
if (!mainLog.isOpen()) return;
if (!mainLogStream) return;
time_t t = time(NULL);
struct tm tm;
@@ -117,6 +120,8 @@ void logWrite(const QString &v) {
{
QMutexLocker lock(&mainLogMutex);
if (!mainLogStream) return;
QString msg(QString("[%1.%2.%3 %4:%5:%6] %7\n").arg(tm.tm_year + 1900).arg(tm.tm_mon + 1, 2, 10, zero).arg(tm.tm_mday, 2, 10, zero).arg(tm.tm_hour, 2, 10, zero).arg(tm.tm_min, 2, 10, zero).arg(tm.tm_sec, 2, 10, zero).arg(v));
(*mainLogStream) << msg;
mainLogStream->flush();
@@ -227,6 +232,8 @@ void logsInit() {
logsInitDebug();
cSetDebug(true);
}
QDir().setCurrent(cWorkingDir());
}
void logsInitDebug() {

View File

@@ -26,6 +26,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "boxes/confirmbox.h"
#include "boxes/stickersetbox.h"
#include "boxes/contactsbox.h"
#include "localstorage.h"
@@ -373,9 +374,9 @@ _failDifferenceTimeout(1), _lastUpdateTime(0), _cachedX(0), _cachedY(0), _backgr
connect(&history, SIGNAL(peerShown(PeerData*)), this, SLOT(onPeerShown(PeerData*)));
connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings()));
connect(this, SIGNAL(showPeerAsync(quint64,qint32,bool,bool)), this, SLOT(showPeer(quint64,qint32,bool,bool)), Qt::QueuedConnection);
if (audioVoice()) {
connect(audioVoice(), SIGNAL(updated(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
connect(audioVoice(), SIGNAL(stopped(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
if (audioPlayer()) {
connect(audioPlayer(), SIGNAL(updated(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
connect(audioPlayer(), SIGNAL(stopped(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
}
connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted()));
@@ -494,9 +495,13 @@ void MainWidget::finishForwarding(History *hist) {
if (_toForward.size() < 2) {
uint64 randomId = MTP::nonce<uint64>();
MsgId newId = clientMsgId();
hist->addToBackForwarded(newId, static_cast<HistoryMessage*>(_toForward.cbegin().value()));
HistoryMessage *msg = static_cast<HistoryMessage*>(_toForward.cbegin().value());
hist->addToBackForwarded(newId, msg);
App::historyRegRandom(randomId, newId);
hist->sendRequestId = MTP::send(MTPmessages_ForwardMessage(hist->peer->input, MTP_int(_toForward.cbegin().key()), MTP_long(randomId)), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
if (HistorySticker *sticker = dynamic_cast<HistorySticker*>(msg->getMedia())) {
App::main()->incrementSticker(sticker->document());
}
} else {
QVector<MTPint> ids;
QVector<MTPlong> randomIds;
@@ -762,19 +767,21 @@ void MainWidget::removeContact(UserData *user) {
void MainWidget::addParticipants(ChatData *chat, const QVector<UserData*> &users) {
for (QVector<UserData*>::const_iterator i = users.cbegin(), e = users.cend(); i != e; ++i) {
MTP::send(MTPmessages_AddChatUser(MTP_int(chat->id & 0xFFFFFFFF), (*i)->inputUser, MTP_int(ForwardOnAdd)), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::addParticipantFail, chat), 0, 5);
MTP::send(MTPmessages_AddChatUser(MTP_int(chat->id & 0xFFFFFFFF), (*i)->inputUser, MTP_int(ForwardOnAdd)), rpcDone(&MainWidget::sentUpdatesReceived), rpcFail(&MainWidget::addParticipantFail, *i), 0, 5);
}
App::wnd()->hideLayer();
showPeer(chat->id, 0, false);
}
bool MainWidget::addParticipantFail(ChatData *chat, const RPCError &error) {
bool MainWidget::addParticipantFail(UserData *user, const RPCError &error) {
if (error.type().startsWith(qsl("FLOOD_WAIT_"))) return false;
ConfirmBox *box = new ConfirmBox(lang(lng_failed_add_participant), true);
App::wnd()->showLayer(box);
QString text = lang(lng_failed_add_participant);
if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
} else if (error.type() == "USER_ALREADY_PARTICIPANT" && user->botInfo) {
text = lang(lng_bot_already_in_group);
}
App::wnd()->showLayer(new ConfirmBox(text, true));
return false;
}
@@ -817,7 +824,7 @@ void MainWidget::checkedHistory(PeerData *peer, const MTPmessages_Messages &resu
dialogs.removePeer(peer);
} else {
History *h = App::historyLoaded(peer->id);
if (!h->last) {
if (!h->lastMsg) {
h->addToBack((*v)[0], 0);
}
}
@@ -929,6 +936,10 @@ DialogsIndexed &MainWidget::contactsList() {
return dialogs.contactsList();
}
DialogsIndexed &MainWidget::dialogsList() {
return dialogs.dialogsList();
}
void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId replyTo, WebPageId webPageId) {
saveRecentHashtags(text);
QString sendingText, leftText = text;
@@ -940,7 +951,7 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl
App::historyRegRandom(randomId, newId);
MTPstring msgText(MTP_string(sendingText));
int32 flags = (hist->peer->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out);
int32 flags = newMessageFlags(hist->peer); // unread, out
int32 sendFlags = 0;
if (replyTo) {
flags |= MTPDmessage::flag_reply_to_msg_id;
@@ -953,8 +964,8 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl
WebPageData *page = App::webPage(webPageId);
media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill)));
}
hist->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(hist->peer->id), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media));
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
hist->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(hist->peer->id), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup));
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
}
finishForwarding(hist);
@@ -963,6 +974,7 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl
void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo) {
readServerHistory(hist, false);
hist->loadAround(0);
if (history.peer())
sendPreparedText(hist, history.prepareMessage(text), replyTo);
}
@@ -1012,6 +1024,14 @@ void MainWidget::stopAnimActive() {
history.stopAnimActive();
}
void MainWidget::sendBotCommand(const QString &cmd, MsgId replyTo) {
history.sendBotCommand(cmd, replyTo);
}
void MainWidget::insertBotCommand(const QString &cmd) {
history.insertBotCommand(cmd);
}
void MainWidget::searchMessages(const QString &query) {
App::wnd()->hideMediaview();
dialogs.searchMessages(query);
@@ -1357,18 +1377,18 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) {
if (audio->loader) {
if (audio->loader->done()) {
audio->finish();
bool mp3 = (audio->mime == QLatin1String("audio/mp3"));
QString already = audio->already();
bool play = !mp3 && audio->openOnSave > 0 && audioVoice();
bool play = audio->openOnSave > 0 && audioPlayer();
if ((!already.isEmpty() && audio->openOnSave) || (!audio->data.isEmpty() && play)) {
if (play) {
AudioData *playing = 0;
VoiceMessageState state = VoiceMessageStopped;
audioVoice()->currentState(&playing, &state);
if (playing == audio && state != VoiceMessageStopped) {
audioVoice()->pauseresume();
AudioPlayerState state = AudioPlayerStopped;
audioPlayer()->currentState(&playing, &state);
if (playing == audio && state != AudioPlayerStopped) {
audioPlayer()->pauseresume();
} else {
audioVoice()->play(audio);
audioPlayer()->play(audio);
if (App::main()) App::main()->audioMarkRead(audio);
}
} else {
QPoint pos(QCursor::pos());
@@ -1377,6 +1397,7 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) {
} else {
psOpenFile(already, audio->openOnSave < 0);
}
if (App::main()) App::main()->audioMarkRead(audio);
}
}
}
@@ -1391,6 +1412,32 @@ void MainWidget::audioLoadProgress(mtpFileLoader *loader) {
}
void MainWidget::audioPlayProgress(AudioData *audio) {
AudioData *playing = 0;
AudioPlayerState state = AudioPlayerStopped;
audioPlayer()->currentState(&playing, &state);
if (playing == audio && state == AudioPlayerStoppedAtStart) {
audioPlayer()->clearStoppedAtStart(audio);
QString already = audio->already(true);
if (already.isEmpty() && !audio->data.isEmpty()) {
bool mp3 = (audio->mime == QLatin1String("audio/mp3"));
QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false);
if (!filename.isEmpty()) {
QFile f(filename);
if (f.open(QIODevice::WriteOnly)) {
if (f.write(audio->data) == audio->data.size()) {
f.close();
already = filename;
audio->location = FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename);
Local::writeFileLocation(mediaKey(mtpToLocationType(mtpc_inputAudioFileLocation), audio->dc, audio->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
}
}
}
}
if (!already.isEmpty()) {
psOpenFile(already);
}
}
const AudioItems &items(App::audioItems());
AudioItems::const_iterator i = items.constFind(audio);
if (i != items.cend()) {
@@ -1428,8 +1475,10 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) {
HistoryItem *item = App::histItemById(document->openOnSaveMsgId);
if (reader.supportsAnimation() && reader.imageCount() > 1 && item) {
startGif(item, already);
} else {
} else if (item) {
App::wnd()->showDocument(document, item);
} else {
psOpenFile(already);
}
} else {
psOpenFile(already);
@@ -1546,7 +1595,7 @@ void MainWidget::serviceNotification(const QString &msg, const MTPMessageMedia &
QString sendingText, leftText = msg;
HistoryItem *item = 0;
while (textSplit(sendingText, leftText, MaxMessageSize)) {
item = App::histories().addToBack(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPint(), MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media), unread ? 1 : 2);
item = App::histories().addToBack(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPint(), MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media, MTPnullMarkup), unread ? 1 : 2);
}
if (item) {
history.peerMessagesUpdated(item->history()->peer->id);
@@ -1736,6 +1785,7 @@ void MainWidget::showPeer(quint64 peerId, qint32 msgId, bool back, bool force) {
if (profile || overview) {
if (profile) {
profile->hide();
profile->clear();
profile->deleteLater();
profile->rpcInvalidate();
profile = 0;
@@ -1859,6 +1909,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool
}
if (profile) {
profile->hide();
profile->clear();
profile->deleteLater();
profile->rpcInvalidate();
profile = 0;
@@ -1906,6 +1957,7 @@ void MainWidget::showPeerProfile(PeerData *peer, bool back, int32 lastScrollTop,
}
if (profile) {
profile->hide();
profile->clear();
profile->deleteLater();
profile->rpcInvalidate();
}
@@ -2030,6 +2082,10 @@ void MainWidget::historyWasRead() {
history.historyWasRead(false);
}
void MainWidget::historyCleared(History *hist) {
history.historyCleared(hist);
}
void MainWidget::animShow(const QPixmap &bgAnimCache, bool back) {
_bgAnimCache = bgAnimCache;
@@ -2462,7 +2518,7 @@ void MainWidget::mtpPing() {
}
void MainWidget::start(const MTPUser &user) {
int32 uid = user.c_userSelf().vid.v;
int32 uid = user.c_user().vid.v;
if (MTP::authedId() != uid) {
MTP::authed(uid);
Local::writeMtpData();
@@ -2490,35 +2546,42 @@ bool MainWidget::started() {
void MainWidget::openLocalUrl(const QString &url) {
QString u(url.trimmed());
if (u.startsWith(QLatin1String("tg://resolve"), Qt::CaseInsensitive)) {
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://resolve/?\\?domain=([a-zA-Z0-9\\.\\_]+)$"), QRegularExpression::CaseInsensitiveOption).match(u);
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://resolve/?\\?domain=([a-zA-Z0-9\\.\\_]+)(&(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+))?(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
if (m.hasMatch()) {
openUserByName(m.captured(1));
QString start = m.captured(3), startToken = m.captured(4);
openUserByName(m.captured(1), (start == qsl("startgroup")), startToken);
}
} else if (u.startsWith(QLatin1String("tg://join"), Qt::CaseInsensitive)) {
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)$"), QRegularExpression::CaseInsensitiveOption).match(u);
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
if (m.hasMatch()) {
joinGroupByHash(m.captured(1));
}
} else if (u.startsWith(QLatin1String("tg://addstickers"), Qt::CaseInsensitive)) {
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)$"), QRegularExpression::CaseInsensitiveOption).match(u);
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
if (m.hasMatch()) {
stickersBox(MTP_inputStickerSetShortName(MTP_string(m.captured(1))));
}
}
}
void MainWidget::openUserByName(const QString &username, bool toProfile) {
void MainWidget::openUserByName(const QString &username, bool toProfile, const QString &startToken) {
App::wnd()->hideMediaview();
UserData *user = App::userByName(username);
if (user) {
if (toProfile) {
showPeerProfile(user);
if (user->botInfo && !user->botInfo->cantJoinGroups && !startToken.isEmpty()) {
user->botInfo->startGroupToken = startToken;
App::wnd()->showLayer(new ContactsBox(user));
} else {
showPeerProfile(user);
}
} else {
if (user->botInfo) user->botInfo->startToken = startToken;
emit showPeerAsync(user->id, 0, false, true);
}
} else {
MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, toProfile), rpcFail(&MainWidget::usernameResolveFail, username));
MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(toProfile, startToken)), rpcFail(&MainWidget::usernameResolveFail, username));
}
}
@@ -2539,13 +2602,19 @@ void MainWidget::onStickersInstalled(uint64 setId) {
history.stickersInstalled(setId);
}
void MainWidget::usernameResolveDone(bool toProfile, const MTPUser &user) {
void MainWidget::usernameResolveDone(QPair<bool, QString> toProfileStartToken, const MTPUser &result) {
App::wnd()->hideLayer();
UserData *u = App::feedUsers(MTP_vector<MTPUser>(1, user));
if (toProfile) {
showPeerProfile(u);
UserData *user = App::feedUsers(MTP_vector<MTPUser>(1, result));
if (toProfileStartToken.first) {
if (user->botInfo && !user->botInfo->cantJoinGroups && !toProfileStartToken.second.isEmpty()) {
user->botInfo->startGroupToken = toProfileStartToken.second;
App::wnd()->showLayer(new ContactsBox(user));
} else {
showPeerProfile(user);
}
} else {
showPeer(u->id, 0, false, true);
if (user->botInfo) user->botInfo->startToken = toProfileStartToken.second;
showPeer(user->id, 0, false, true);
}
}
@@ -2637,7 +2706,7 @@ bool MainWidget::inviteImportFail(const RPCError &error) {
void MainWidget::startFull(const MTPVector<MTPUser> &users) {
const QVector<MTPUser> &v(users.c_vector().v);
if (v.isEmpty() || v[0].type() != mtpc_userSelf) { // wtf?..
if (v.isEmpty() || v[0].type() != mtpc_user || !(v[0].c_user().vflags.v & MTPDuser_flag_self)) { // wtf?..
return App::logOut();
}
start(v[0]);
@@ -3025,7 +3094,7 @@ void MainWidget::handleUpdates(const MTPUpdates &updates) {
return;
}
bool out = (d.vflags.v & MTPDmessage_flag_out);
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, out ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(out ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty()));
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, out ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(out ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup));
if (item) {
history.peerMessagesUpdated(item->history()->peer->id);
}
@@ -3045,7 +3114,7 @@ void MainWidget::handleUpdates(const MTPUpdates &updates) {
_byPtsUpdates.insert(ptsKey(SkippedUpdates), updates);
return;
}
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty()));
HistoryItem *item = App::histories().addToBack(MTP_message(d.vflags, d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from_id, d.vfwd_date, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup));
if (item) {
history.peerMessagesUpdated(item->history()->peer->id);
}
@@ -3190,7 +3259,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
case mtpc_updateChatParticipants: {
const MTPDupdateChatParticipants &d(update.c_updateChatParticipants());
App::feedParticipants(d.vparticipants);
App::feedParticipants(d.vparticipants, true);
} break;
case mtpc_updateChatParticipantAdd: {

View File

@@ -187,7 +187,7 @@ public:
void start(const MTPUser &user);
void openLocalUrl(const QString &str);
void openUserByName(const QString &name, bool toProfile = false);
void openUserByName(const QString &name, bool toProfile = false, const QString &startToken = QString());
void joinGroupByHash(const QString &hash);
void stickersBox(const MTPInputStickerSet &set);
@@ -216,6 +216,7 @@ public:
void dialogsToUp();
void newUnreadMsg(History *history, HistoryItem *item);
void historyWasRead();
void historyCleared(History *history);
void peerBefore(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg);
void peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPeer, MsgId &outMsg);
@@ -270,7 +271,7 @@ public:
void removeContact(UserData *user);
void addParticipants(ChatData *chat, const QVector<UserData*> &users);
bool addParticipantFail(ChatData *chat, const RPCError &e);
bool addParticipantFail(UserData *user, const RPCError &e);
void kickParticipant(ChatData *chat, UserData *user);
bool kickParticipantFail(ChatData *chat, const RPCError &e);
@@ -285,6 +286,7 @@ public:
void clearSelectedItems();
DialogsIndexed &contactsList();
DialogsIndexed &dialogsList();
void sendMessage(History *history, const QString &text, MsgId replyTo);
void sendPreparedText(History *hist, const QString &text, MsgId replyTo, WebPageId webPageId = 0);
@@ -295,6 +297,9 @@ public:
uint64 animActiveTime() const;
void stopAnimActive();
void sendBotCommand(const QString &cmd, MsgId msgId);
void insertBotCommand(const QString &cmd);
void searchMessages(const QString &query);
void preloadOverviews(PeerData *peer);
void mediaOverviewUpdated(PeerData *peer);
@@ -447,7 +452,7 @@ private:
void handleUpdates(const MTPUpdates &updates);
bool updateFail(const RPCError &e);
void usernameResolveDone(bool toProfile, const MTPUser &user);
void usernameResolveDone(QPair<bool, QString> toProfileStartToken, const MTPUser &result);
bool usernameResolveFail(QString name, const RPCError &error);
void inviteCheckDone(QString hash, const MTPChatInvite &invite);

View File

@@ -824,6 +824,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
if (_current.isNull() && _currentGif.isNull()) {
if (_doc->thumb->isNull()) {
style::sprite thumbs[] = { st::mvDocBlue, st::mvDocGreen, st::mvDocRed, st::mvDocYellow };
style::color colors[] = { st::mvDocBlueColor, st::mvDocGreenColor, st::mvDocRedColor, st::mvDocYellowColor };
QString name = _doc->name.toLower(), mime = _doc->mime.toLower();
if (name.endsWith(QLatin1String(".doc")) ||
name.endsWith(QLatin1String(".txt")) ||
@@ -831,17 +832,20 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
mime.startsWith(QLatin1String("text/"))
) {
_docIcon = thumbs[0];
_docIconColor = colors[0];
} else if (
name.endsWith(QLatin1String(".xls")) ||
name.endsWith(QLatin1String(".csv"))
) {
_docIcon = thumbs[1];
_docIconColor = colors[1];
} else if (
name.endsWith(QLatin1String(".pdf")) ||
name.endsWith(QLatin1String(".ppt")) ||
name.endsWith(QLatin1String(".key"))
) {
_docIcon = thumbs[2];
_docIconColor = colors[2];
} else if (
name.endsWith(QLatin1String(".zip")) ||
name.endsWith(QLatin1String(".rar")) ||
@@ -851,10 +855,12 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) {
name.endsWith(QLatin1String(".avi"))
) {
_docIcon = thumbs[3];
_docIconColor = colors[3];
} else {
int ext = name.lastIndexOf('.');
QChar ch = (ext >= 0 && ext + 1 < name.size()) ? name.at(ext + 1) : (name.isEmpty() ? (mime.isEmpty() ? '0' : mime.at(0)) : name.at(0));
_docIcon = thumbs[ch.unicode() % 4];
_docIconColor = colors[ch.unicode() % 4];
}
} else {
_doc->thumb->load();
@@ -1074,11 +1080,13 @@ void MediaView::paintEvent(QPaintEvent *e) {
if (_docIconRect.intersects(r)) {
icon = true;
if (_doc->thumb->isNull()) {
p.drawPixmap(_docIconRect.topLeft(), App::sprite(), _docIcon);
if (!_doc->already().isEmpty() && (!_docRadialStart || _docRadialOpacity < 1)) {
p.drawPixmap(_docIconRect.topLeft(), App::sprite(), _docIcon);
p.setPen(st::mvDocExtColor->p);
p.setFont(st::mvDocExtFont->f);
p.drawText(_docIconRect.x() + (_docIconRect.width() - _docExtWidth) / 2, _docIconRect.y() + st::mvDocExtTop + st::mvDocExtFont->ascent, _docExt);
} else {
p.fillRect(_docIconRect, _docIconColor->b);
}
} else {
int32 rf(cIntRetinaFactor());

View File

@@ -140,6 +140,7 @@ private:
int32 _full; // -1 - thumb, 0 - medium, 1 - full
style::sprite _docIcon;
style::color _docIconColor;
QString _docName, _docSize, _docExt;
int32 _docNameWidth, _docSizeWidth, _docExtWidth;
QRect _docRect, _docIconRect;

View File

@@ -743,8 +743,9 @@ void MTPautoConnection::disconnectFromServer() {
status = FinishedWork;
}
void MTPautoConnection::connectToServer(const QString &addr, int32 port) {
address = QUrl(qsl("http://%1:%2/api").arg(addr).arg(80));//not port - always 80 port for http transport
void MTPautoConnection::connectToServer(const QString &addr, int32 port, int32 flags) {
address = QUrl(((flags & MTPDdcOption_flag_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
_addr = addr;
@@ -943,7 +944,7 @@ void MTPtcpConnection::disconnectFromServer() {
sock.close();
}
void MTPtcpConnection::connectToServer(const QString &addr, int32 port) {
void MTPtcpConnection::connectToServer(const QString &addr, int32 port, int32 flags) {
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(addr), port);
}
@@ -1013,8 +1014,9 @@ void MTPhttpConnection::disconnectFromServer() {
address = QUrl();
}
void MTPhttpConnection::connectToServer(const QString &addr, int32 p) {
address = QUrl(qsl("http://%1:%2/api").arg(addr).arg(80));//not p - always 80 port for http transport
void MTPhttpConnection::connectToServer(const QString &addr, int32 p, int32 flags) {
address = QUrl(((flags & MTPDdcOption_flag_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
emit connected();
}
@@ -1062,19 +1064,30 @@ QString MTPhttpConnection::transport() const {
return qsl("HTTP");
}
void MTProtoConnectionPrivate::createConn() {
if (conn) {
conn->deleteLater();
void MTProtoConnectionPrivate::createConn(bool createIPv4, bool createIPv6) {
destroyConn();
if (createIPv4) {
if (cConnectionType() == dbictAuto) {
_conn4 = new MTPautoConnection(thread());
} else if (cConnectionType() == dbictTcpProxy) {
_conn4 = new MTPtcpConnection(thread());
} else {
_conn4 = new MTPhttpConnection(thread());
}
connect(_conn4, SIGNAL(error(bool)), this, SLOT(onError4(bool)));
connect(_conn4, SIGNAL(receivedSome()), this, SLOT(onReceivedSome()));
}
if (cConnectionType() == dbictAuto) {
conn = new MTPautoConnection(thread());
} else if (cConnectionType() == dbictTcpProxy) {
conn = new MTPtcpConnection(thread());
} else {
conn = new MTPhttpConnection(thread());
if (createIPv6) {
if (cConnectionType() == dbictAuto) {
_conn6 = new MTPautoConnection(thread());
} else if (cConnectionType() == dbictTcpProxy) {
_conn6 = new MTPtcpConnection(thread());
} else {
_conn6 = new MTPhttpConnection(thread());
}
connect(_conn6, SIGNAL(error(bool)), this, SLOT(onError6(bool)));
connect(_conn6, SIGNAL(receivedSome()), this, SLOT(onReceivedSome()));
}
connect(conn, SIGNAL(error(bool)), this, SLOT(onError(bool)));
connect(conn, SIGNAL(receivedSome()), this, SLOT(onReceivedSome()));
firstSentAt = 0;
if (oldConnection) {
oldConnection = false;
@@ -1083,16 +1096,35 @@ void MTProtoConnectionPrivate::createConn() {
oldConnectionTimer.start(MTPConnectionOldTimeout);
}
void MTProtoConnectionPrivate::destroyConn(MTPabstractConnection **conn) {
if (conn) {
if (*conn) {
disconnect(*conn, SIGNAL(disconnected()), 0, 0);
disconnect(*conn, SIGNAL(receivedData()), 0, 0);
disconnect(*conn, SIGNAL(receivedSome()), 0, 0);
(*conn)->disconnectFromServer();
(*conn)->deleteLater();
*conn = 0;
}
} else {
destroyConn(&_conn4);
destroyConn(&_conn6);
_conn = 0;
}
}
MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConnection *owner, MTPSessionData *data, uint32 _dc)
: QObject(0)
, _state(MTProtoConnection::Disconnected)
, _needSessionReset(false)
, dc(_dc)
, _owner(owner)
, conn(0)
, _conn(0), _conn4(0), _conn6(0)
, retryTimeout(1)
, oldConnection(true)
, receiveDelay(MTPMinReceiveDelay)
, _waitForReceived(MTPMinReceiveDelay)
, _waitForConnected(MTPMinConnectDelay)
, firstSentAt(-1)
, _pingId(0)
, _pingIdToSend(0)
@@ -1106,13 +1138,13 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
, authKeyStrings(0) {
oldConnectionTimer.moveToThread(thread);
connCheckTimer.moveToThread(thread);
_waitForConnectedTimer.moveToThread(thread);
_waitForReceivedTimer.moveToThread(thread);
_waitForIPv4Timer.moveToThread(thread);
_pingSender.moveToThread(thread);
retryTimer.moveToThread(thread);
moveToThread(thread);
// createConn();
if (!dc) {
QReadLocker lock(mtpDcOptionsMutex());
const mtpDcOptions &options(cDcOptions());
@@ -1129,7 +1161,9 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
connect(thread, SIGNAL(finished()), this, SLOT(doFinish()));
connect(&retryTimer, SIGNAL(timeout()), this, SLOT(retryByTimer()));
connect(&connCheckTimer, SIGNAL(timeout()), this, SLOT(onBadConnection()));
connect(&_waitForConnectedTimer, SIGNAL(timeout()), this, SLOT(onWaitConnectedFailed()));
connect(&_waitForReceivedTimer, SIGNAL(timeout()), this, SLOT(onWaitReceivedFailed()));
connect(&_waitForIPv4Timer, SIGNAL(timeout()), this, SLOT(onWaitIPv4Failed()));
connect(&oldConnectionTimer, SIGNAL(timeout()), this, SLOT(onOldConnection()));
connect(&_pingSender, SIGNAL(timeout()), this, SLOT(onPingSender()));
connect(sessionData->owner(), SIGNAL(authKeyCreated()), this, SLOT(updateAuthKey()), Qt::QueuedConnection);
@@ -1180,10 +1214,10 @@ int32 MTProtoConnectionPrivate::getState() const {
}
QString MTProtoConnectionPrivate::transport() const {
if (!conn || _state < 0) {
if ((!_conn4 && !_conn6) || _state < 0) {
return QString();
}
return conn->transport();
return (_conn4 ? _conn4 : _conn6)->transport();
}
bool MTProtoConnectionPrivate::setState(int32 state, int32 ifState) {
@@ -1418,7 +1452,7 @@ mtpMsgId MTProtoConnectionPrivate::placeToContainer(mtpRequest &toSendRequest, m
void MTProtoConnectionPrivate::tryToSend() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData || !conn) {
if (!sessionData || !_conn) {
return;
}
@@ -1510,7 +1544,7 @@ void MTProtoConnectionPrivate::tryToSend() {
stateRequest->msDate = getms(true); // > 0 - can send without container
stateRequest->requestId = reqid();// add to haveSent / wereAcked maps, but don't add to requestMap
}
if (conn->usingHttpWait()) {
if (_conn->usingHttpWait()) {
MTPHttpWait req(MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000)));
httpWaitRequest = mtpRequestData::prepare(req.innerLength() >> 2);
@@ -1731,44 +1765,63 @@ void MTProtoConnectionPrivate::restartNow() {
}
void MTProtoConnectionPrivate::socketStart(bool afterConfig) {
if (!conn) createConn();
if (conn->isConnected()) {
onConnected();
return;
}
setState(MTProtoConnection::Connecting);
_pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0;
_pingSender.stop();
std::string ip;
uint32 port = 0;
int32 flags4 = 0, flags6 = 0;
std::string ip4, ip6;
uint32 port4 = 0, port6 = 0;
{
QReadLocker lock(mtpDcOptionsMutex());
const mtpDcOptions &options(cDcOptions());
mtpDcOptions::const_iterator dcIndex = options.constFind(dc % _mtp_internal::dcShift);
DEBUG_LOG(("MTP Info: connecting to DC %1..").arg(dc));
if (dcIndex != options.cend()) {
ip = dcIndex->ip;
port = dcIndex->port;
mtpDcOptions::const_iterator dcIndex4 = options.constFind(dc % _mtp_internal::dcShift);
if (dcIndex4 != options.cend()) {
ip4 = dcIndex4->ip;
flags4 = dcIndex4->flags;
port4 = dcIndex4->port;
}
mtpDcOptions::const_iterator dcIndex6 = options.constFind((dc % _mtp_internal::dcShift) + (_mtp_internal::dcShift * MTPDdcOption_flag_ipv6));
if (dcIndex6 != options.cend()) {
ip6 = dcIndex6->ip;
flags6 = dcIndex6->flags;
port6 = dcIndex6->port;
}
}
if (!port || ip.empty()) {
bool noIPv4 = (!port4 || ip4.empty()), noIPv6 = (!port6 || ip6.empty());
if (noIPv4 && noIPv6) {
if (afterConfig) {
LOG(("MTP Error: DC %1 options not found right after config load!").arg(dc));
if (noIPv4) LOG(("MTP Error: DC %1 options for IPv4 not found right after config load!").arg(dc));
if (noIPv6) LOG(("MTP Error: DC %1 options for IPv6 not found right after config load!").arg(dc));
return restart();
}
DEBUG_LOG(("MTP Info: DC %1 options not found, waiting for config").arg(dc));
if (noIPv4) DEBUG_LOG(("MTP Info: DC %1 options for IPv4 not found, waiting for config").arg(dc));
if (noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 not found, waiting for config").arg(dc));
connect(mtpConfigLoader(), SIGNAL(loaded()), this, SLOT(onConfigLoaded()));
mtpConfigLoader()->load();
return;
}
DEBUG_LOG(("MTP Info: socket connection to %1:%2..").arg(ip.c_str()).arg(port));
connect(conn, SIGNAL(connected()), this, SLOT(onConnected()));
connect(conn, SIGNAL(disconnected()), this, SLOT(restart()));
if (afterConfig && (_conn4 || _conn6)) return;
conn->connectToServer(ip.c_str(), port);
createConn(!noIPv4, !noIPv6);
retryTimer.stop();
_waitForConnectedTimer.stop();
setState(MTProtoConnection::Connecting);
_pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0;
_pingSender.stop();
if (!noIPv4) DEBUG_LOG(("MTP Info: creating IPv4 connection to %1:%2..").arg(ip4.c_str()).arg(port4));
if (!noIPv6) DEBUG_LOG(("MTP Info: creating IPv6 connection to [%1]:%2..").arg(ip6.c_str()).arg(port6));
_waitForConnectedTimer.start(_waitForConnected);
if (_conn4) {
connect(_conn4, SIGNAL(connected()), this, SLOT(onConnected4()));
connect(_conn4, SIGNAL(disconnected()), this, SLOT(onDisconnected4()));
_conn4->connectToServer(ip4.c_str(), port4, flags4);
}
if (_conn6) {
connect(_conn6, SIGNAL(connected()), this, SLOT(onConnected6()));
connect(_conn6, SIGNAL(disconnected()), this, SLOT(onDisconnected6()));
_conn6->connectToServer(ip6.c_str(), port6, flags6);
}
}
void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
@@ -1777,7 +1830,8 @@ void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
DEBUG_LOG(("MTP Info: restarting MTProtoConnection, maybe bad key = %1").arg(logBool(maybeBadKey)));
connCheckTimer.stop();
_waitForReceivedTimer.stop();
_waitForConnectedTimer.stop();
mtpAuthKeyPtr key(sessionData->getKey());
if (key) {
@@ -1805,12 +1859,12 @@ void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
}
void MTProtoConnectionPrivate::onSentSome(uint64 size) {
if (!connCheckTimer.isActive()) {
uint64 remain = receiveDelay;
if (!_waitForReceivedTimer.isActive()) {
uint64 remain = _waitForReceived;
if (!oldConnection) {
uint64 remainBySize = size * receiveDelay / 8192; // 8kb / sec, so 512 kb give 64 sec
uint64 remainBySize = size * _waitForReceived / 8192; // 8kb / sec, so 512 kb give 64 sec
remain = snap(remainBySize, remain, uint64(MTPMaxReceiveDelay));
if (remain != receiveDelay) {
if (remain != _waitForReceived) {
DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain));
}
}
@@ -1819,7 +1873,7 @@ void MTProtoConnectionPrivate::onSentSome(uint64 size) {
} else if (dc >= MTP::dld[0] && dc < MTP::dld[MTPDownloadSessionsCount - 1] + _mtp_internal::dcShift) {
remain *= MTPDownloadSessionsCount;
}
connCheckTimer.start(remain);
_waitForReceivedTimer.start(remain);
}
if (!firstSentAt) firstSentAt = getms(true);
}
@@ -1830,20 +1884,20 @@ void MTProtoConnectionPrivate::onReceivedSome() {
DEBUG_LOG(("This connection marked as not old!"));
}
oldConnectionTimer.start(MTPConnectionOldTimeout);
connCheckTimer.stop();
_waitForReceivedTimer.stop();
if (firstSentAt > 0) {
int32 ms = getms(true) - firstSentAt;
DEBUG_LOG(("MTP Info: response in %1ms, receiveDelay: %2ms").arg(ms).arg(receiveDelay));
DEBUG_LOG(("MTP Info: response in %1ms, _waitForReceived: %2ms").arg(ms).arg(_waitForReceived));
if (ms > 0 && ms * 2 < int32(receiveDelay)) receiveDelay = qMax(ms * 2, int32(MTPMinReceiveDelay));
if (ms > 0 && ms * 2 < int32(_waitForReceived)) _waitForReceived = qMax(ms * 2, int32(MTPMinReceiveDelay));
firstSentAt = -1;
}
}
void MTProtoConnectionPrivate::onOldConnection() {
oldConnection = true;
receiveDelay = MTPMinReceiveDelay;
DEBUG_LOG(("This connection marked as old! delay now %1ms").arg(receiveDelay));
_waitForReceived = MTPMinReceiveDelay;
DEBUG_LOG(("This connection marked as old! _waitForReceived now %1ms").arg(_waitForReceived));
}
void MTProtoConnectionPrivate::onPingSender() {
@@ -1867,14 +1921,14 @@ void MTProtoConnectionPrivate::onPingSendForce() {
}
}
void MTProtoConnectionPrivate::onBadConnection() {
void MTProtoConnectionPrivate::onWaitReceivedFailed() {
if (cConnectionType() != dbictAuto && cConnectionType() != dbictTcpProxy) {
return;
}
DEBUG_LOG(("MTP Info: bad connection, delay: %1ms").arg(receiveDelay));
if (receiveDelay < MTPMaxReceiveDelay) {
receiveDelay *= 2;
DEBUG_LOG(("MTP Info: bad connection, _waitForReceived: %1ms").arg(_waitForReceived));
if (_waitForReceived < MTPMaxReceiveDelay) {
_waitForReceived *= 2;
}
doDisconnect();
restarted = true;
@@ -1884,16 +1938,32 @@ void MTProtoConnectionPrivate::onBadConnection() {
QTimer::singleShot(0, this, SLOT(socketStart()));
}
void MTProtoConnectionPrivate::doDisconnect() {
if (conn) {
disconnect(conn, SIGNAL(disconnected()), 0, 0);
disconnect(conn, SIGNAL(receivedData()), 0, 0);
disconnect(conn, SIGNAL(receivedSome()), 0, 0);
void MTProtoConnectionPrivate::onWaitConnectedFailed() {
DEBUG_LOG(("MTP Info: can't connect in %1ms").arg(_waitForConnected));
if (_waitForConnected < MTPMaxConnectDelay) _waitForConnected *= 2;
conn->disconnectFromServer();
conn->deleteLater();
conn = 0;
doDisconnect();
restarted = true;
DEBUG_LOG(("MTP Info: immediate restart!"));
QTimer::singleShot(0, this, SLOT(socketStart()));
}
void MTProtoConnectionPrivate::onWaitIPv4Failed() {
_conn = _conn6;
destroyConn(&_conn4);
if (_conn) {
DEBUG_LOG(("MTP Info: can't connect through IPv4, using IPv6 connection."));
updateAuthKey();
} else {
restart();
}
}
void MTProtoConnectionPrivate::doDisconnect() {
destroyConn();
unlockKey();
@@ -1928,9 +1998,8 @@ void MTProtoConnectionPrivate::handleReceived() {
return restart();
}
while (conn->received().size()) {
const mtpBuffer &encryptedBuf(conn->received().front());
while (_conn->received().size()) {
const mtpBuffer &encryptedBuf(_conn->received().front());
uint32 len = encryptedBuf.size();
const mtpPrime *encrypted(encryptedBuf.data());
if (len < 18) { // 2 auth_key_id, 4 msg_key, 2 salt, 2 session, 2 msg_id, 1 seq_no, 1 length, (1 data + 3 padding) min
@@ -1958,14 +2027,14 @@ void MTProtoConnectionPrivate::handleReceived() {
if (uint32(dataBuffer.size()) < msgLen + 8 * sizeof(mtpPrime) || (msgLen & 0x03)) {
LOG(("TCP Error: bad msg_len received %1, data size: %2").arg(msgLen).arg(dataBuffer.size()));
TCP_LOG(("TCP Error: bad message %1").arg(mb(encrypted, len * sizeof(mtpPrime)).str()));
conn->received().pop_front();
_conn->received().pop_front();
return restart();
}
uchar sha1Buffer[20];
if (memcmp(&msgKey, hashSha1(data, msgLen + 8 * sizeof(mtpPrime), sha1Buffer) + 1, sizeof(msgKey))) {
LOG(("TCP Error: bad SHA1 hash after aesDecrypt in message"));
TCP_LOG(("TCP Error: bad message %1").arg(mb(encrypted, len * sizeof(mtpPrime)).str()));
conn->received().pop_front();
_conn->received().pop_front();
return restart();
}
TCP_LOG(("TCP Info: decrypted message %1,%2,%3 is %4 len").arg(msgId).arg(seqNo).arg(logBool(needAck)).arg(msgLen + 8 * sizeof(mtpPrime)));
@@ -1974,11 +2043,11 @@ void MTProtoConnectionPrivate::handleReceived() {
if (session != serverSession) {
LOG(("MTP Error: bad server session received"));
TCP_LOG(("MTP Error: bad server session %1 instead of %2 in message received").arg(session).arg(serverSession));
conn->received().pop_front();
_conn->received().pop_front();
return restart();
}
conn->received().pop_front();
_conn->received().pop_front();
int32 serverTime((int32)(msgId >> 32)), clientTime(unixtime());
bool isReply = ((msgId & 0x03) == 1);
@@ -2061,6 +2130,7 @@ void MTProtoConnectionPrivate::handleReceived() {
_needSessionReset = (res < -1);
return restart();
}
retryTimeout = 1; // reset restart() timer
if (!sessionData->isCheckedKey()) {
DEBUG_LOG(("MTP Info: marked auth key as checked"));
@@ -2073,7 +2143,7 @@ void MTProtoConnectionPrivate::handleReceived() {
}
}
}
if (conn->needHttpWait()) {
if (_conn->needHttpWait()) {
emit sendHttpWaitAsync();
}
}
@@ -2570,8 +2640,6 @@ int32 MTProtoConnectionPrivate::handleOneReceived(const mtpPrime *from, const mt
}
}
requestsAcked(ids, true);
retryTimeout = 1; // reset restart() timer
} return 1;
}
@@ -2818,24 +2886,72 @@ void MTProtoConnectionPrivate::resendMany(QVector<quint64> msgIds, quint64 msCan
emit resendManyAsync(msgIds, msCanWait, forceContainer, sendMsgStateInfo);
}
void MTProtoConnectionPrivate::onConnected() {
void MTProtoConnectionPrivate::onConnected4() {
_waitForConnected = MTPMinConnectDelay;
_waitForConnectedTimer.stop();
_waitForIPv4Timer.stop();
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
disconnect(conn, SIGNAL(connected()), this, SLOT(onConnected()));
if (!conn->isConnected()) {
LOG(("Connection Error: not connected in onConnected(), state: %1").arg(conn->debugState()));
disconnect(_conn4, SIGNAL(connected()), this, SLOT(onConnected4()));
if (!_conn4->isConnected()) {
LOG(("Connection Error: not connected in onConnected4(), state: %1").arg(_conn4->debugState()));
return restart();
}
TCP_LOG(("Connection Info: connection succeed."));
_conn = _conn4;
destroyConn(&_conn6);
DEBUG_LOG(("MTP Info: connection through IPv4 succeed."));
updateAuthKey();
}
void MTProtoConnectionPrivate::onConnected6() {
_waitForConnected = MTPMinConnectDelay;
_waitForConnectedTimer.stop();
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
disconnect(_conn6, SIGNAL(connected()), this, SLOT(onConnected()));
if (!_conn6->isConnected()) {
LOG(("Connection Error: not connected in onConnected(), state: %1").arg(_conn6->debugState()));
return restart();
}
DEBUG_LOG(("MTP Info: connection through IPv6 succeed, waiting IPv4 for %1ms.").arg(MTPIPv4ConnectionWaitTimeout));
_waitForIPv4Timer.start(MTPIPv4ConnectionWaitTimeout);
}
void MTProtoConnectionPrivate::onDisconnected4() {
if (_conn && _conn == _conn6) return; // disconnected the unused
if (_conn || !_conn6) {
destroyConn();
restart();
} else {
destroyConn(&_conn4);
}
}
void MTProtoConnectionPrivate::onDisconnected6() {
if (_conn && _conn == _conn4) return; // disconnected the unused
if (_conn || !_conn4) {
destroyConn();
restart();
} else {
destroyConn(&_conn6);
}
}
void MTProtoConnectionPrivate::updateAuthKey() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData || !conn) return;
if (!sessionData || !_conn) return;
DEBUG_LOG(("AuthKey Info: MTProtoConnection updating key from MTProtoSession, dc %1").arg(dc));
uint64 newKeyId = 0;
@@ -2878,20 +2994,20 @@ void MTProtoConnectionPrivate::updateAuthKey() {
MTPReq_pq req_pq;
req_pq.vnonce = authKeyData->nonce;
connect(conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
connect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
DEBUG_LOG(("AuthKey Info: sending Req_pq.."));
sendRequestNotSecure(req_pq);
}
void MTProtoConnectionPrivate::clearMessages() {
if (keyId && keyId != mtpAuthKey::RecreateKeyId && conn) {
conn->received().clear();
if (keyId && keyId != mtpAuthKey::RecreateKeyId && _conn) {
_conn->received().clear();
}
}
void MTProtoConnectionPrivate::pqAnswered() {
disconnect(conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
disconnect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered()));
DEBUG_LOG(("AuthKey Info: receiving Req_pq answer.."));
MTPReq_pq::ResponseType res_pq;
@@ -2987,14 +3103,14 @@ void MTProtoConnectionPrivate::pqAnswered() {
return restart();
}
connect(conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
connect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
DEBUG_LOG(("AuthKey Info: sending Req_DH_params.."));
sendRequestNotSecure(req_DH_params);
}
void MTProtoConnectionPrivate::dhParamsAnswered() {
disconnect(conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered()));
DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer.."));
MTPReq_DH_params::ResponseType res_DH_params;
@@ -3169,7 +3285,7 @@ void MTProtoConnectionPrivate::dhClientParamsSend() {
aesEncrypt(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), authKeyData->aesKey, authKeyData->aesIV);
connect(conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
connect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params.."));
sendRequestNotSecure(req_client_DH_params);
@@ -3179,7 +3295,7 @@ void MTProtoConnectionPrivate::dhClientParamsAnswered() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
disconnect(conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer.."));
MTPSet_client_DH_params::ResponseType res_client_DH_params;
@@ -3274,7 +3390,7 @@ void MTProtoConnectionPrivate::dhClientParamsAnswered() {
void MTProtoConnectionPrivate::authKeyCreated() {
clearAuthKeyData();
connect(conn, SIGNAL(receivedData()), this, SLOT(handleReceived()));
connect(_conn, SIGNAL(receivedData()), this, SLOT(handleReceived()));
if (sessionData->getSalt()) { // else receive salt in bad_server_salt first, then try to send all the requests
setState(MTProtoConnection::Connected);
@@ -3307,9 +3423,32 @@ void MTProtoConnectionPrivate::clearAuthKeyData() {
}
}
void MTProtoConnectionPrivate::onError(bool mayBeBadKey) {
MTP_LOG(dc, ("Restarting after error, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
return restart(mayBeBadKey);
void MTProtoConnectionPrivate::onError4(bool mayBeBadKey) {
if (_conn && _conn == _conn6) return; // error in the unused
if (_conn || !_conn6) {
destroyConn();
_waitForConnectedTimer.stop();
MTP_LOG(dc, ("Restarting after error in IPv4 connection, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
return restart(mayBeBadKey);
} else {
destroyConn(&_conn4);
}
}
void MTProtoConnectionPrivate::onError6(bool mayBeBadKey) {
if (_conn && _conn == _conn4) return; // error in the unused
if (_conn || !_conn4) {
destroyConn();
_waitForConnectedTimer.stop();
MTP_LOG(dc, ("Restarting after error in IPv6 connection, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
return restart(mayBeBadKey);
} else {
destroyConn(&_conn6);
}
}
void MTProtoConnectionPrivate::onReadyData() {
@@ -3336,7 +3475,7 @@ void MTProtoConnectionPrivate::sendRequestNotSecure(const TRequest &request) {
DEBUG_LOG(("AuthKey Info: sending request, size: %1, num: %2, time: %3").arg(requestSize).arg(authKeyData->req_num).arg(buffer[5]));
conn->sendData(buffer);
_conn->sendData(buffer);
onSentSome(buffer.size() * sizeof(mtpPrime));
@@ -3350,12 +3489,12 @@ bool MTProtoConnectionPrivate::readResponseNotSecure(TResponse &response) {
onReceivedSome();
try {
if (conn->received().isEmpty()) {
if (_conn->received().isEmpty()) {
LOG(("AuthKey Error: trying to read response from empty received list"));
return false;
}
mtpBuffer buffer(conn->received().front());
conn->received().pop_front();
mtpBuffer buffer(_conn->received().front());
_conn->received().pop_front();
const mtpPrime *answer(buffer.constData());
uint32 len = buffer.size();
@@ -3426,8 +3565,8 @@ bool MTProtoConnectionPrivate::sendRequest(mtpRequest &request, bool needAnyResp
DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5]));
conn->setSentEncrypted();
conn->sendData(result);
_conn->setSentEncrypted();
_conn->sendData(result);
if (needAnyResponse) {
onSentSome(result.size() * sizeof(mtpPrime));

View File

@@ -25,9 +25,27 @@ enum {
MTPDmessage_flag_out = (1 << 1),
MTPDmessage_flag_notify_by_from = (1 << 4),
MTPDmessage_flag_media_unread = (1 << 5),
MTPmessages_SendMessage_flag_skipWebPage = (1 << 1),
MTPDdcOption_flag_ipv6 = (1 << 0),
MTPDdcOption_flag_files = (1 << 1),
MTPDuser_flag_self = (1 << 10),
MTPDuser_flag_contact = (1 << 11),
MTPDuser_flag_mutual_contact = (1 << 12),
MTPDuser_flag_deleted = (1 << 13),
MTPDuser_flag_bot = (1 << 14),
MTPDuser_flag_bot_reads_all = (1 << 15),
MTPDuser_flag_bot_cant_join = (1 << 16),
MTPDreplyKeyboardMarkup_flag_resize = (1 << 0),
MTPDreplyKeyboardMarkup_flag_single_use = (1 << 1),
MTPDreplyKeyboardMarkup_flag_ZERO = (1 << 31) // client side flag for zeroMarkup
};
static const MTPReplyMarkup MTPnullMarkup = MTP_replyKeyboardMarkup(MTP_int(0), MTP_vector<MTPKeyboardButtonRow>(0));
#include "mtproto/mtpPublicRSA.h"
#include "mtproto/mtpAuthKey.h"
@@ -97,11 +115,6 @@ public:
int32 state() const;
QString transport() const;
/*template <typename TRequest> // not used
uint64 sendAsync(const TRequest &request) {
return data->sendAsync(request);
}*/
private:
QThread *thread;
@@ -125,7 +138,7 @@ public:
virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32
virtual void disconnectFromServer() = 0;
virtual void connectToServer(const QString &addr, int32 port) = 0;
virtual void connectToServer(const QString &addr, int32 port, int32 flags) = 0;
virtual bool isConnected() = 0;
virtual bool usingHttpWait() {
return false;
@@ -193,7 +206,7 @@ public:
void sendData(mtpBuffer &buffer);
void disconnectFromServer();
void connectToServer(const QString &addr, int32 port);
void connectToServer(const QString &addr, int32 port, int32 flags);
bool isConnected();
bool usingHttpWait();
bool needHttpWait();
@@ -255,7 +268,7 @@ public:
void sendData(mtpBuffer &buffer);
void disconnectFromServer();
void connectToServer(const QString &addr, int32 port);
void connectToServer(const QString &addr, int32 port, int32 flags);
bool isConnected();
int32 debugState() const;
@@ -281,7 +294,7 @@ public:
void sendData(mtpBuffer &buffer);
void disconnectFromServer();
void connectToServer(const QString &addr, int32 port);
void connectToServer(const QString &addr, int32 port, int32 flags);
bool isConnected();
bool usingHttpWait();
bool needHttpWait();
@@ -343,15 +356,25 @@ public slots:
void onPingSender();
void onPingSendForce();
void onBadConnection();
void onWaitConnectedFailed();
void onWaitReceivedFailed();
void onWaitIPv4Failed();
void onOldConnection();
void onSentSome(uint64 size);
void onReceivedSome();
void onError(bool maybeBadKey = false);
void onReadyData();
void socketStart(bool afterConfig = false);
void onConnected();
void onConnected4();
void onConnected6();
void onDisconnected4();
void onDisconnected6();
void onError4(bool maybeBadKey = false);
void onError6(bool maybeBadKey = false);
void doDisconnect();
void doFinish();
@@ -372,7 +395,8 @@ public slots:
private:
void createConn();
void createConn(bool createIPv4, bool createIPv6);
void destroyConn(MTPabstractConnection **conn = 0); // 0 - destory all
mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req);
mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId);
@@ -396,7 +420,7 @@ private:
uint32 dc;
MTProtoConnection *_owner;
MTPabstractConnection *conn;
MTPabstractConnection *_conn, *_conn4, *_conn6;
SingleTimer retryTimer; // exp retry timer
uint32 retryTimeout;
@@ -405,8 +429,8 @@ private:
SingleTimer oldConnectionTimer;
bool oldConnection;
SingleTimer connCheckTimer;
uint32 receiveDelay;
SingleTimer _waitForConnectedTimer, _waitForReceivedTimer, _waitForIPv4Timer;
uint32 _waitForReceived, _waitForConnected;
int64 firstSentAt;
QVector<MTPlong> ackRequestData, resendRequestData;

View File

@@ -366,7 +366,7 @@ static const mtpTypeId mtpLayers[] = {
mtpc_invokeWithLayer17,
mtpc_invokeWithLayer18,
}, mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
static const mtpPrime mtpCurrentLayer = 29;
static const mtpPrime mtpCurrentLayer = 31;
template <typename bareT>
class MTPBoxed : public bareT {

View File

@@ -175,15 +175,16 @@ void mtpUpdateDcOptions(const QVector<MTPDcOption> &options) {
}
for (QVector<MTPDcOption>::const_iterator i = options.cbegin(), e = options.cend(); i != e; ++i) {
const MTPDdcOption &optData(i->c_dcOption());
if (already.constFind(optData.vid.v) == already.cend()) {
already.insert(optData.vid.v);
mtpDcOptions::const_iterator a = opts.constFind(optData.vid.v);
int32 id = optData.vid.v, idWithShift = id + (optData.vflags.v * _mtp_internal::dcShift);
if (already.constFind(idWithShift) == already.cend()) {
already.insert(idWithShift);
mtpDcOptions::const_iterator a = opts.constFind(idWithShift);
if (a != opts.cend()) {
if (a.value().ip != optData.vip_address.c_string().v || a.value().port != optData.vport.v) {
restart.insert(optData.vid.v);
restart.insert(id);
}
}
opts.insert(optData.vid.v, mtpDcOption(optData.vid.v, optData.vhostname.c_string().v, optData.vip_address.c_string().v, optData.vport.v));
opts.insert(idWithShift, mtpDcOption(id, optData.vflags.v, optData.vip_address.c_string().v, optData.vport.v));
}
}
{

View File

@@ -1109,96 +1109,24 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
}
break;
case mtpc_userSelf:
case mtpc_user:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ userSelf");
to.add("{ user");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" phone: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 6: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_userContact:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ userContact");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" phone: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 7: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_userRequest:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ userRequest");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" phone: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 6: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 7: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_userForeign:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ userForeign");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" access_hash: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 6: to.add(" status: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_userDeleted:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ userDeleted");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" username: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" access_hash: "); ++stages.back(); if (flag & MTPDuser::flag_access_hash) { types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
case 3: to.add(" first_name: "); ++stages.back(); if (flag & MTPDuser::flag_first_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
case 4: to.add(" last_name: "); ++stages.back(); if (flag & MTPDuser::flag_last_name) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
case 5: to.add(" username: "); ++stages.back(); if (flag & MTPDuser::flag_username) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 3 IN FIELD flags ]"); } break;
case 6: to.add(" phone: "); ++stages.back(); if (flag & MTPDuser::flag_phone) { types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 4 IN FIELD flags ]"); } break;
case 7: to.add(" photo: "); ++stages.back(); if (flag & MTPDuser::flag_photo) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 5 IN FIELD flags ]"); } break;
case 8: to.add(" status: "); ++stages.back(); if (flag & MTPDuser::flag_status) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break;
case 9: to.add(" bot_info_version: "); ++stages.back(); if (flag & MTPDuser::flag_bot_info_version) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 14 IN FIELD flags ]"); } break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -1347,6 +1275,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
case 2: to.add(" chat_photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" notify_settings: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" exported_invite: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" bot_info: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -1444,6 +1373,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
case 7: to.add(" date: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 8: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 9: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 10: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPDmessage::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 6 IN FIELD flags ]"); } break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -1893,8 +1823,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" expires: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" user: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 0: to.add(" user: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -2048,8 +1977,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
case 2: to.add(" profile_photo: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" notify_settings: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" blocked: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" real_first_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 6: to.add(" real_last_name: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" bot_info: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -2404,6 +2332,10 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
to.add("{ inputMessagesFilterAudio }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
break;
case mtpc_inputMessagesFilterAudioDocuments:
to.add("{ inputMessagesFilterAudioDocuments }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
break;
case mtpc_updateNewMessage:
if (stage) {
to.add(",\n").addSpaces(lev);
@@ -3057,8 +2989,8 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" hostname: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" ip_address: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" port: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
@@ -4353,6 +4285,86 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
}
break;
case mtpc_botCommand:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ botCommand");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" command: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" params: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_botInfoEmpty:
to.add("{ botInfoEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
break;
case mtpc_botInfo:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ botInfo");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" version: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" share_text: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" description: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" commands: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_keyboardButton:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ keyboardButton");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" text: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_keyboardButtonRow:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ keyboardButtonRow");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" buttons: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_replyKeyboardHide:
to.add("{ replyKeyboardHide }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
break;
case mtpc_replyKeyboardMarkup:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ replyKeyboardMarkup");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" rows: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_req_pq:
if (stage) {
to.add(",\n").addSpaces(lev);
@@ -5018,6 +5030,22 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
}
break;
case mtpc_auth_importBotAuthorization:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ auth_importBotAuthorization");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" flags: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" api_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" api_hash: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" bot_auth_token: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_auth_checkPassword:
if (stage) {
to.add(",\n").addSpaces(lev);
@@ -5388,6 +5416,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
case 2: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
case 3: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPmessages_sendMessage::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -5405,6 +5434,7 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
case 2: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendMedia::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
case 3: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 5: to.add(" reply_markup: "); ++stages.back(); if (flag & MTPmessages_sendMedia::flag_reply_markup) { types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
@@ -5539,6 +5569,22 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP
}
break;
case mtpc_messages_startBot:
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ messages_startBot");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" bot: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" chat_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" start_param: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
break;
case mtpc_messages_getChats:
if (stage) {
to.add(",\n").addSpaces(lev);

File diff suppressed because it is too large Load Diff

View File

@@ -193,11 +193,6 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL
fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
userEmpty#200250ba id:int = User;
userSelf#1c60e608 id:int first_name:string last_name:string username:string phone:string photo:UserProfilePhoto status:UserStatus = User;
userContact#cab35e18 id:int first_name:string last_name:string username:string access_hash:long phone:string photo:UserProfilePhoto status:UserStatus = User;
userRequest#d9ccc4ef id:int first_name:string last_name:string username:string access_hash:long phone:string photo:UserProfilePhoto status:UserStatus = User;
userForeign#75cf7a8 id:int first_name:string last_name:string username:string access_hash:long photo:UserProfilePhoto status:UserStatus = User;
userDeleted#d6016d7a id:int first_name:string last_name:string username:string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto;
@@ -210,7 +205,7 @@ chatEmpty#9ba2d800 id:int = Chat;
chat#6e9c9bc7 id:int title:string photo:ChatPhoto participants_count:int date:int left:Bool version:int = Chat;
chatForbidden#fb0ccc41 id:int title:string date:int = Chat;
chatFull#cade0791 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite = ChatFull;
chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
@@ -221,7 +216,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto;
messageEmpty#83e5de54 id:int = Message;
message#a7ab1991 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:MessageMedia = Message;
message#c3060325 flags:# id:int from_id:int to_id:Peer fwd_from_id:flags.2?int fwd_date:flags.2?int reply_to_msg_id:flags.3?int date:int message:string media:MessageMedia reply_markup:flags.6?ReplyMarkup = Message;
messageService#1d86f70e flags:int id:int from_id:int to_id:Peer date:int action:MessageAction = Message;
messageMediaEmpty#3ded6320 = MessageMedia;
@@ -258,7 +253,7 @@ auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone;
auth.sentCode#efed51d9 phone_registered:Bool phone_code_hash:string send_call_timeout:int is_password:Bool = auth.SentCode;
auth.authorization#f6b673a4 expires:int user:User = auth.Authorization;
auth.authorization#ff036af1 user:User = auth.Authorization;
auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization;
@@ -280,7 +275,7 @@ peerNotifySettings#8d5e11ee mute_until:int sound:string show_previews:Bool event
wallPaper#ccb03657 id:int title:string sizes:Vector<PhotoSize> color:int = WallPaper;
userFull#771095da user:User link:contacts.Link profile_photo:Photo notify_settings:PeerNotifySettings blocked:Bool real_first_name:string real_last_name:string = UserFull;
userFull#5a89ac5b user:User link:contacts.Link profile_photo:Photo notify_settings:PeerNotifySettings blocked:Bool bot_info:BotInfo = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact;
@@ -329,6 +324,7 @@ inputMessagesFilterPhotoVideo#56e9f0e4 = MessagesFilter;
inputMessagesFilterPhotoVideoDocuments#d95e73bb = MessagesFilter;
inputMessagesFilterDocument#9eddf188 = MessagesFilter;
inputMessagesFilterAudio#cfc87522 = MessagesFilter;
inputMessagesFilterAudioDocuments#5afbf764 = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
updateMessageID#4e90bfd6 id:int random_id:long = Update;
@@ -363,7 +359,7 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
dcOption#2ec2a43c id:int hostname:string ip_address:string port:int = DcOption;
dcOption#5d8c6cc flags:# id:int ip_address:string port:int = DcOption;
config#4e32b894 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int broadcast_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int disabled_features:Vector<DisabledFeature> = Config;
@@ -596,6 +592,20 @@ stickerSet#a7a43b17 id:long access_hash:long title:string short_name:string = St
messages.stickerSet#b60a24a6 set:StickerSet packs:Vector<StickerPack> documents:Vector<Document> = messages.StickerSet;
user#22e49072 flags:# id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int = User;
botCommand#b79d22ab command:string params:string description:string = BotCommand;
botInfoEmpty#bb2e37ce = BotInfo;
botInfo#9cf585d user_id:int version:int share_text:string description:string commands:Vector<BotCommand> = BotInfo;
keyboardButton#a2fa4880 text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyKeyboardHide#ced6ebbc = ReplyMarkup;
replyKeyboardMarkup#3502758c flags:# rows:Vector<KeyboardButtonRow> = ReplyMarkup;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -647,8 +657,8 @@ messages.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHis
messages.deleteMessages#a5f18925 id:Vector<int> = messages.AffectedMessages;
messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
messages.sendMessage#9add8f26 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long = messages.SentMessage;
messages.sendMedia#2d7923b1 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long = Updates;
messages.sendMessage#fc55e6b5 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup = messages.SentMessage;
messages.sendMedia#c8f16791 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long reply_markup:flags.2?ReplyMarkup = Updates;
messages.forwardMessages#55e1728d peer:InputPeer id:Vector<int> random_id:Vector<long> = Updates;
messages.getChats#3c6aa187 id:Vector<int> = messages.Chats;
messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull;
@@ -736,6 +746,8 @@ messages.getAllStickers#aa3bc868 hash:string = messages.AllStickers;
account.updateDeviceLocked#38df3532 period:int = Bool;
auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
messages.getWebPagePreview#25223e24 message:string = MessageMedia;
account.getAuthorizations#e320c158 = account.Authorizations;
@@ -756,3 +768,4 @@ messages.importChatInvite#6c50051c hash:string = Updates;
messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet;
messages.installStickerSet#efbbfae9 stickerset:InputStickerSet = Bool;
messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool;
messages.startBot#1b3e0ffc bot:InputUser chat_id:int random_id:long start_param:string = Updates;

View File

@@ -39,9 +39,16 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
_addParticipant(this, lang(lng_profile_add_participant), st::btnShareContact),
_sendMessage(this, lang(lng_profile_send_message), st::btnShareContact),
_shareContact(this, lang(lng_profile_share_contact), st::btnShareContact),
_inviteToGroup(this, lang(lng_profile_invite_to_group), st::btnShareContact),
_cancelPhoto(this, lang(lng_cancel)),
_createInvitationLink(this, lang(lng_group_invite_create)),
_invitationLink(this, qsl("telegram.me/joinchat/")),
_botSettings(this, lang(lng_profile_bot_settings)),
_botHelp(this, lang(lng_profile_bot_help)),
// about
_about(st::wndMinWidth - st::profilePadding.left() - st::profilePadding.right()),
_aboutTop(0), _aboutHeight(0),
a_photo(0),
_photoOver(false),
@@ -66,7 +73,7 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
_menu(0) {
connect(App::api(), SIGNAL(fullPeerLoaded(PeerData*)), this, SLOT(onFullPeerLoaded(PeerData*)));
connect(App::api(), SIGNAL(fullPeerUpdated(PeerData*)), this, SLOT(onFullPeerUpdated(PeerData*)));
if (_peerUser) {
_phoneText = _peerUser->phone.isEmpty() ? QString() : App::formatPhone(_peerUser->phone);
@@ -87,12 +94,34 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
connect(&_addParticipant, SIGNAL(clicked()), this, SLOT(onAddParticipant()));
connect(&_sendMessage, SIGNAL(clicked()), this, SLOT(onSendMessage()));
connect(&_shareContact, SIGNAL(clicked()), this, SLOT(onShareContact()));
connect(&_inviteToGroup, SIGNAL(clicked()), this, SLOT(onInviteToGroup()));
connect(&_cancelPhoto, SIGNAL(clicked()), this, SLOT(onUpdatePhotoCancel()));
connect(&_createInvitationLink, SIGNAL(clicked()), this, SLOT(onCreateInvitationLink()));
connect(&_invitationLink, SIGNAL(clicked()), this, SLOT(onInvitationLink()));
_invitationLink.setAcceptBoth(true);
updateInvitationLink();
if (_peerChat) {
QString maxStr = lang(_uploadPhoto.textWidth() > _addParticipant.textWidth() ? lng_profile_set_group_photo : lng_profile_add_participant);
_uploadPhoto.setAutoFontSize(st::profileMinBtnPadding, maxStr);
_uploadPhoto.setAutoFontSize(st::profileMinBtnPadding, maxStr);
} else if (_peerUser) {
QString maxStr;
if (_peerUser->botInfo && !_peerUser->botInfo->cantJoinGroups) {
maxStr = lang(_sendMessage.textWidth() > _inviteToGroup.textWidth() ? lng_profile_send_message : lng_profile_invite_to_group);
} else if (!_peerUser->phone.isEmpty()) {
maxStr = lang(_sendMessage.textWidth() > _shareContact.textWidth() ? lng_profile_send_message : lng_profile_share_contact);
} else {
maxStr = lang(lng_profile_send_message);
}
_sendMessage.setAutoFontSize(st::profileMinBtnPadding, maxStr);
_shareContact.setAutoFontSize(st::profileMinBtnPadding, maxStr);
_inviteToGroup.setAutoFontSize(st::profileMinBtnPadding, maxStr);
}
connect(&_botSettings, SIGNAL(clicked()), this, SLOT(onBotSettings()));
connect(&_botHelp, SIGNAL(clicked()), this, SLOT(onBotHelp()));
connect(App::app(), SIGNAL(peerPhotoDone(PeerId)), this, SLOT(onPhotoUpdateDone(PeerId)));
connect(App::app(), SIGNAL(peerPhotoFail(PeerId)), this, SLOT(onPhotoUpdateFail(PeerId)));
@@ -100,6 +129,17 @@ ProfileInner::ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const Pee
connect(App::main(), SIGNAL(peerUpdated(PeerData *)), this, SLOT(peerUpdated(PeerData *)));
connect(App::main(), SIGNAL(peerNameChanged(PeerData *, const PeerData::Names &, const PeerData::NameFirstChars &)), this, SLOT(peerUpdated(PeerData *)));
// about
if (_peerUser && _peerUser->botInfo) {
if (!_peerUser->botInfo->shareText.isEmpty()) {
_about.setText(st::linkFont, _peerUser->botInfo->shareText, _historyBotOptions);
}
updateBotLinksVisibility();
} else {
_botSettings.hide();
_botHelp.hide();
}
// settings
connect(&_enableNotifications, SIGNAL(clicked()), this, SLOT(onEnableNotifications()));
connect(&_clearHistory, SIGNAL(clicked()), this, SLOT(onClearHistory()));
@@ -126,6 +166,10 @@ void ProfileInner::onShareContact() {
App::main()->shareContactLayer(_peerUser);
}
void ProfileInner::onInviteToGroup() {
App::wnd()->showLayer(new ContactsBox(_peerUser));
}
void ProfileInner::onSendMessage() {
App::main()->showPeer(_peer->id);
}
@@ -279,7 +323,7 @@ void ProfileInner::chatInviteDone(const MTPExportedChatInvite &result) {
App::wnd()->hideLayer();
}
void ProfileInner::onFullPeerLoaded(PeerData *peer) {
void ProfileInner::onFullPeerUpdated(PeerData *peer) {
if (peer != _peer) return;
if (_peerUser) {
PhotoData *userPhoto = _peerUser->photoId ? App::photo(_peerUser->photoId) : 0;
@@ -288,6 +332,15 @@ void ProfileInner::onFullPeerLoaded(PeerData *peer) {
} else {
_photoLink = TextLinkPtr();
}
if (_peerUser->botInfo) {
if (_peerUser->botInfo->shareText.isEmpty()) {
_about = Text(st::wndMinWidth - st::profilePadding.left() - st::profilePadding.right());
} else {
_about.setText(st::linkFont, _peerUser->botInfo->shareText, _historyBotOptions);
}
updateBotLinksVisibility();
resizeEvent(0);
}
} else if (_peerChat) {
updateInvitationLink();
showAll();
@@ -295,6 +348,30 @@ void ProfileInner::onFullPeerLoaded(PeerData *peer) {
}
}
void ProfileInner::onBotSettings() {
for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) {
QString cmd = _peerUser->botInfo->commands.at(i).command;
if (!cmd.compare(qsl("settings"), Qt::CaseInsensitive)) {
App::main()->showPeer(_peer->id);
App::main()->sendBotCommand('/' + cmd, 0);
return;
}
}
updateBotLinksVisibility();
}
void ProfileInner::onBotHelp() {
for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) {
QString cmd = _peerUser->botInfo->commands.at(i).command;
if (!cmd.compare(qsl("help"), Qt::CaseInsensitive)) {
App::main()->showPeer(_peer->id);
App::main()->sendBotCommand('/' + cmd, 0);
return;
}
}
updateBotLinksVisibility();
}
void ProfileInner::peerUpdated(PeerData *data) {
if (data == _peer) {
PhotoData *photo = 0;
@@ -325,13 +402,13 @@ void ProfileInner::updateOnlineDisplayTimer() {
if (_peerChat->participants.isEmpty()) return;
for (ChatData::Participants::const_iterator i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) {
int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key()->onlineTill, t);
int32 onlineWillChangeIn = App::onlineWillChangeIn(i.key(), t);
if (onlineWillChangeIn < minIn) {
minIn = onlineWillChangeIn;
}
}
} else {
minIn = App::onlineWillChangeIn(_peerUser->onlineTill, t);
minIn = App::onlineWillChangeIn(_peerUser, t);
}
App::main()->updateOnlineDisplayIn(minIn * 1000);
}
@@ -354,13 +431,13 @@ void ProfileInner::reorderParticipants() {
bool onlyMe = true;
for (ChatData::Participants::const_iterator i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
int32 until = App::onlineForSort(user->onlineTill, t);
int32 until = App::onlineForSort(user, t);
Participants::iterator before = _participants.begin();
if (user != self) {
if (before != _participants.end() && (*before) == self) {
++before;
}
while (before != _participants.end() && App::onlineForSort((*before)->onlineTill, t) >= until) {
while (before != _participants.end() && App::onlineForSort(*before, t) >= until) {
++before;
}
if (until > t && onlyMe) onlyMe = false;
@@ -392,6 +469,9 @@ void ProfileInner::reorderParticipants() {
}
}
void ProfileInner::start() {
}
bool ProfileInner::event(QEvent *e) {
if (e->type() == QEvent::MouseMove) {
_lastPos = static_cast<QMouseEvent*>(e)->globalPos();
@@ -446,7 +526,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
if (!_errorText.isEmpty()) {
p.setFont(st::setErrFont->f);
p.setPen(st::setErrColor->p);
p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, top + addbyname + st::profilePhoneTop + st::profilePhoneFont->ascent, _errorText);
p.drawText(_left + st::profilePhotoSize + st::profilePhoneLeft, _cancelPhoto.y() + addbyname + st::profilePhoneFont->ascent, _errorText);
}
if (!_phoneText.isEmpty()) {
p.setPen(st::black->p);
@@ -464,6 +544,17 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
}
top += _shareContact.height();
// about
if (!_about.isEmpty()) {
p.setFont(st::profileHeaderFont->f);
p.setPen(st::profileHeaderColor->p);
p.drawText(_left + st::profileHeaderLeft, top + st::profileHeaderTop + st::profileHeaderFont->ascent, lang(lng_profile_about_section));
top += st::profileHeaderSkip;
_about.draw(p, _left, top, _width);
top += _aboutHeight;
}
// settings
p.setFont(st::profileHeaderFont->f);
p.setPen(st::profileHeaderColor->p);
@@ -537,7 +628,15 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
if (!data) {
data = _participantsData[cnt] = new ParticipantData();
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user, l_time);
if (user->botInfo) {
if (user->botInfo->readsAllHistory) {
data->online = lang(lng_status_bot_reads_all);
} else {
data->online = lang(lng_status_bot_not_reads_all);
}
} else {
data->online = App::onlineText(user, l_time);
}
data->cankick = (user != App::self()) && (_chatAdmin || (_peerChat->cankick.constFind(user) != _peerChat->cankick.cend()));
}
p.setPen(st::profileListNameColor->p);
@@ -555,7 +654,7 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
} else {
p.setPen(st::btnDefLink.color->p);
}
p.drawText(_left + _width - _kickWidth, top + (_pHeight - st::linkFont->height) / 2 + st::linkFont->ascent, lang(lng_profile_kick));
p.drawText(_left + _width - _kickWidth, top + st::profileListNameTop + st::linkFont->ascent, lang(lng_profile_kick));
}
}
top += fullCnt * _pHeight;
@@ -579,9 +678,9 @@ void ProfileInner::mouseMoveEvent(QMouseEvent *e) {
}
}
if (!_photoLink && (!_peerChat || _peerChat->forbidden)) {
setCursor((_kickOver || _kickDown) ? style::cur_pointer : style::cur_default);
setCursor((_kickOver || _kickDown || textlnkOver()) ? style::cur_pointer : style::cur_default);
} else {
setCursor((_kickOver || _kickDown || _photoOver) ? style::cur_pointer : style::cur_default);
setCursor((_kickOver || _kickDown || _photoOver || textlnkOver()) ? style::cur_pointer : style::cur_default);
}
}
@@ -590,6 +689,16 @@ void ProfileInner::updateSelected() {
QPoint lp = mapFromGlobal(_lastPos);
TextLinkPtr lnk;
bool inText = false;
if (!_about.isEmpty() && lp.y() >= _aboutTop && lp.y() < _aboutTop + _aboutHeight && lp.x() >= _left && lp.x() < _left + _width) {
_about.getState(lnk, inText, lp.x() - _left, lp.y() - _aboutTop, _width);
}
if (textlnkOver() != lnk) {
textlnkOver(lnk);
update(QRect(_left, _aboutTop, _width, _aboutHeight));
}
int32 partfrom = _mediaAudios.y() + _mediaAudios.height() + st::profileHeaderSkip;
int32 newSelected = (lp.x() >= _left - st::profileListPadding.width() && lp.x() < _left + _width + st::profileListPadding.width() && lp.y() >= partfrom) ? (lp.y() - partfrom) / _pHeight : -1;
@@ -597,7 +706,7 @@ void ProfileInner::updateSelected() {
if (newSelected >= 0 && newSelected < _participants.size()) {
ParticipantData *data = _participantsData[newSelected];
if (data && data->cankick) {
int32 top = partfrom + newSelected * _pHeight + (_pHeight - st::linkFont->height) / 2;
int32 top = partfrom + newSelected * _pHeight + st::profileListNameTop;
if ((lp.x() >= _left + _width - _kickWidth) && (lp.x() < _left + _width) && (lp.y() >= top) && (lp.y() < top + st::linkFont->height)) {
newKickOver = _participants[newSelected];
}
@@ -631,18 +740,31 @@ void ProfileInner::mousePressEvent(QMouseEvent *e) {
onUpdatePhoto();
}
}
textlnkDown(textlnkOver());
}
}
void ProfileInner::mouseReleaseEvent(QMouseEvent *e) {
_lastPos = e->globalPos();
updateSelected();
if (_kickDown && _kickDown == _kickOver) {
_kickConfirm = _kickOver;
ConfirmBox *box = new ConfirmBox(lng_profile_sure_kick(lt_user, _kickOver->firstName));
connect(box, SIGNAL(confirmed()), this, SLOT(onKickConfirm()));
App::wnd()->showLayer(box);
}
if (textlnkDown()) {
TextLinkPtr lnk = textlnkDown();
textlnkDown(TextLinkPtr());
if (lnk == textlnkOver()) {
if (reBotCommand().match(lnk->encoded()).hasMatch()) {
App::main()->showPeer(_peer->id);
}
lnk->onClick(e->button());
}
}
_kickDown = 0;
setCursor(_kickOver ? style::cur_pointer : style::cur_default);
setCursor((_kickOver || textlnkOver()) ? style::cur_pointer : style::cur_default);
update();
}
@@ -697,16 +819,30 @@ void ProfileInner::resizeEvent(QResizeEvent *e) {
_cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhotoSize - st::linkFont->height);
} else {
_cancelPhoto.move(_left + _width - _cancelPhoto.width(), top + st::profilePhoneTop);
_botSettings.move(_left + st::profilePhotoSize + st::profilePhoneLeft, top + st::profileStatusTop + st::linkFont->ascent - (st::profileNameTop + st::profileNameFont->ascent) + st::profilePhoneTop);
_botHelp.move(_botSettings.x() + (_botSettings.isHidden() ? 0 : _botSettings.width() + st::profilePhoneLeft), _botSettings.y());
}
top += st::profilePhotoSize;
top += st::profileButtonTop;
_uploadPhoto.setGeometry(_left, top, btnWidth, _uploadPhoto.height());
_sendMessage.setGeometry(_left, top, btnWidth, _sendMessage.height());
_addParticipant.setGeometry(_left + _width - btnWidth, top, btnWidth, _addParticipant.height());
_sendMessage.setGeometry(_left, top, btnWidth, _sendMessage.height());
_shareContact.setGeometry(_left + _width - btnWidth, top, btnWidth, _shareContact.height());
_inviteToGroup.setGeometry(_left + _width - btnWidth, top, btnWidth, _inviteToGroup.height());
top += _shareContact.height();
// about
if (!_about.isEmpty()) {
top += st::profileHeaderSkip;
_aboutTop = top; _aboutHeight = _about.countHeight(_width); top += _aboutHeight;
} else {
_aboutTop = _aboutHeight = 0;
}
// settings
top += st::profileHeaderSkip;
_enableNotifications.move(_left, top); top += _enableNotifications.height();
@@ -829,6 +965,7 @@ void ProfileInner::showAll() {
if (_peerChat) {
_sendMessage.hide();
_shareContact.hide();
_inviteToGroup.hide();
if (_peerChat->forbidden) {
_uploadPhoto.hide();
_cancelPhoto.hide();
@@ -871,8 +1008,14 @@ void ProfileInner::showAll() {
_sendMessage.show();
if (_peerUser->phone.isEmpty()) {
_shareContact.hide();
if (_peerUser->botInfo && !_peerUser->botInfo->cantJoinGroups) {
_inviteToGroup.show();
} else {
_inviteToGroup.hide();
}
} else {
_shareContact.show();
_inviteToGroup.hide();
}
_enableNotifications.show();
_clearHistory.show();
@@ -944,6 +1087,23 @@ void ProfileInner::updateInvitationLink() {
}
}
void ProfileInner::updateBotLinksVisibility() {
if (!_peerUser || !_peerUser->botInfo || _peerUser->botInfo->commands.isEmpty()) {
_botSettings.hide();
_botHelp.hide();
return;
}
bool hasSettings = false, hasHelp = false;
for (int32 i = 0, l = _peerUser->botInfo->commands.size(); i != l; ++i) {
QString cmd = _peerUser->botInfo->commands.at(i).command;
hasSettings |= !cmd.compare(qsl("settings"), Qt::CaseInsensitive);
hasHelp |= !cmd.compare(qsl("help"), Qt::CaseInsensitive);
if (hasSettings && hasHelp) break;
}
_botSettings.setVisible(hasSettings);
_botHelp.setVisible(hasHelp);
}
QString ProfileInner::overviewLinkText(int32 type, int32 count) {
switch (type) {
case OverviewPhotos: return lng_profile_photos(lt_count, count);
@@ -1063,6 +1223,7 @@ bool ProfileWidget::animStep(float64 ms) {
_bgAnimCache = _animCache = _animTopBarCache = _bgAnimTopBarCache = QPixmap();
App::main()->topBar()->stopAnim();
_scroll.show();
_inner.start();
activate();
} else {
a_bgCoord.update(dt1, st::introHideFunc);
@@ -1092,6 +1253,12 @@ void ProfileWidget::mediaOverviewUpdated(PeerData *peer) {
_inner.mediaOverviewUpdated(peer);
}
void ProfileWidget::clear() {
if (_inner.peer() && !_inner.peer()->chat && _inner.peer()->asUser()->botInfo) {
_inner.peer()->asUser()->botInfo->startGroupToken = QString();
}
}
ProfileWidget::~ProfileWidget() {
}

View File

@@ -25,6 +25,8 @@ public:
ProfileInner(ProfileWidget *profile, ScrollArea *scroll, const PeerData *peer);
void start();
bool event(QEvent *e);
void paintEvent(QPaintEvent *e);
void mouseMoveEvent(QMouseEvent *e);
@@ -64,6 +66,7 @@ public slots:
void deleteContextImage();
void onShareContact();
void onInviteToGroup();
void onSendMessage();
void onEnableNotifications();
@@ -94,12 +97,16 @@ public slots:
void onCreateInvitationLink();
void onCreateInvitationLinkSure();
void onFullPeerLoaded(PeerData *peer);
void onFullPeerUpdated(PeerData *peer);
void onBotSettings();
void onBotHelp();
private:
void showAll();
void updateInvitationLink();
void updateBotLinksVisibility();
void chatInviteDone(const MTPExportedChatInvite &result);
@@ -120,9 +127,13 @@ private:
QString _phoneText;
TextLinkPtr _photoLink;
FlatButton _uploadPhoto, _addParticipant;
FlatButton _sendMessage, _shareContact;
FlatButton _sendMessage, _shareContact, _inviteToGroup;
LinkButton _cancelPhoto, _createInvitationLink, _invitationLink;
QString _invitationText;
LinkButton _botSettings, _botHelp;
Text _about;
int32 _aboutTop, _aboutHeight;
anim::fvalue a_photo;
bool _photoOver;
@@ -191,6 +202,7 @@ public:
void updateNotifySettings();
void mediaOverviewUpdated(PeerData *peer);
void clear();
~ProfileWidget();
public slots:

View File

@@ -43,6 +43,29 @@ extern "C" {
#include <unity/unity/unity.h>
namespace {
QString escapeShell(const QString &str) {
QString result;
const QChar *b = str.constData(), *e = str.constEnd();
for (const QChar *ch = b; ch != e; ++ch) {
if (*ch == ' ' || *ch == '"' || *ch == '\'' || *ch == '\\') {
if (result.isEmpty()) {
result.reserve(str.size() * 2);
}
if (ch > b) {
result.append(b, ch - b);
}
result.append('\\');
b = ch;
}
}
if (result.isEmpty()) return str;
if (e > b) {
result.append(b, e - b);
}
return result;
}
bool frameless = true;
bool finished = true;
bool noQtTrayIcon = false;
@@ -923,183 +946,6 @@ PsApplication::~PsApplication() {
_psEventFilter = 0;
}
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : reply(0), already(0), full(0) {
updateUrl = qs(update.vurl);
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
updateUrl = url;
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
void PsUpdateDownloader::initOutput() {
QString fileName;
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
if (m.hasMatch()) {
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand());
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
QFileInfo file(fileName);
QDir dir(dirStr);
if (dir.exists()) {
QFileInfoList all = dir.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
if (i->absoluteFilePath() != file.absoluteFilePath()) {
QFile::remove(i->absoluteFilePath());
}
}
} else {
dir.mkdir(dir.absolutePath());
}
outputFile.setFileName(fileName);
if (file.exists()) {
uint64 fullSize = file.size();
if (fullSize < INT_MAX) {
int32 goodSize = (int32)fullSize;
if (goodSize % UpdateChunk) {
goodSize = goodSize - (goodSize % UpdateChunk);
if (goodSize) {
if (outputFile.open(QIODevice::ReadOnly)) {
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
outputFile.close();
if (outputFile.open(QIODevice::WriteOnly)) {
outputFile.write(goodData);
outputFile.close();
QMutexLocker lock(&mutex);
already = goodSize;
}
}
}
} else {
QMutexLocker lock(&mutex);
already = goodSize;
}
}
if (!already) {
QFile::remove(fileName);
}
}
}
void PsUpdateDownloader::start() {
sendRequest();
}
void PsUpdateDownloader::sendRequest() {
QNetworkRequest req(updateUrl);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1);
req.setRawHeader("Range", rangeHeaderValue);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
if (reply) reply->deleteLater();
reply = manager.get(req);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
}
void PsUpdateDownloader::partMetaGot() {
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
Pairs pairs = reply->rawHeaderPairs();
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
if (QString::fromUtf8(i->first).toLower() == "content-range") {
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
if (m.hasMatch()) {
{
QMutexLocker lock(&mutex);
full = m.captured(1).toInt();
}
emit App::app()->updateDownloading(already, full);
}
}
}
}
int32 PsUpdateDownloader::ready() {
QMutexLocker lock(&mutex);
return already;
}
int32 PsUpdateDownloader::size() {
QMutexLocker lock(&mutex);
return full;
}
void PsUpdateDownloader::partFinished(qint64 got, qint64 total) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status != 200 && status != 206 && status != 416) {
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
return fatalFail();
}
}
if (!already && !full) {
QMutexLocker lock(&mutex);
full = total;
}
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
if (!outputFile.isOpen()) {
if (!outputFile.open(QIODevice::Append)) {
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
return fatalFail();
}
}
QByteArray r = reply->readAll();
if (!r.isEmpty()) {
outputFile.write(r);
QMutexLocker lock(&mutex);
already += r.size();
}
if (got >= total) {
reply->deleteLater();
reply = 0;
outputFile.close();
unpackUpdate();
} else {
emit App::app()->updateDownloading(already, full);
}
}
void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
reply = 0;
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status == 416) { // Requested range not satisfiable
outputFile.close();
unpackUpdate();
return;
}
}
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
emit App::app()->updateFailed();
}
bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/questions/2256945/removing-a-non-empty-directory-programmatically-in-c-or-c
QByteArray pathRaw = QFile::encodeName(path);
DIR *d = opendir(pathRaw.constData());
@@ -1131,236 +977,8 @@ bool _removeDirectory(const QString &path) { // from http://stackoverflow.com/qu
return !rmdir(pathRaw.constData());
}
void PsUpdateDownloader::deleteDir(const QString &dir) {
_removeDirectory(dir);
}
void PsUpdateDownloader::fatalFail() {
clearAll();
emit App::app()->updateFailed();
}
void PsUpdateDownloader::clearAll() {
deleteDir(cWorkingDir() + qsl("tupdates"));
}
#ifdef Q_OS_WIN
typedef DWORD VerInt;
typedef WCHAR VerChar;
#else
typedef int VerInt;
typedef wchar_t VerChar;
#endif
void PsUpdateDownloader::unpackUpdate() {
QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
return fatalFail();
}
#ifdef Q_OS_WIN // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
#else
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
#endif
QByteArray compressed = outputFile.readAll();
int32 compressedLen = compressed.size() - hSize;
if (compressedLen <= 0) {
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
return fatalFail();
}
outputFile.close();
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready");
deleteDir(tempDirPath);
deleteDir(readyDirPath);
QDir tempDir(tempDirPath), readyDir(readyDirPath);
if (tempDir.exists() || readyDir.exists()) {
LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!"));
return fatalFail();
}
uchar sha1Buffer[20];
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
if (!goodSha1) {
LOG(("Update Error: bad SHA1 hash of update file!"));
return fatalFail();
}
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
if (!pbKey) {
LOG(("Update Error: cant read public rsa key!"));
return fatalFail();
}
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
RSA_free(pbKey);
LOG(("Update Error: bad RSA signature of update file!"));
return fatalFail();
}
RSA_free(pbKey);
QByteArray uncompressed;
int32 uncompressedLen;
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
uncompressed.resize(uncompressedLen);
size_t resultLen = uncompressed.size();
#ifdef Q_OS_WIN // use Lzma SDK for win
SizeT srcLen = compressedLen;
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
if (uncompressRes != SZ_OK) {
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
return fatalFail();
}
#else
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
if (ret != LZMA_OK) {
const char *msg;
switch (ret) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
return fatalFail();
}
stream.avail_in = compressedLen;
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
stream.avail_out = resultLen;
stream.next_out = (uint8_t*)uncompressed.data();
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
if (stream.avail_in) {
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
return fatalFail();
} else if (stream.avail_out) {
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
return fatalFail();
}
lzma_end(&stream);
if (res != LZMA_OK && res != LZMA_STREAM_END) {
const char *msg;
switch (res) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
return fatalFail();
}
#endif
tempDir.mkdir(tempDir.absolutePath());
quint32 version;
{
QBuffer buffer(&uncompressed);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
stream >> version;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (int32(version) <= AppVersion) {
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
return fatalFail();
}
quint32 filesCount;
stream >> filesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (!filesCount) {
LOG(("Update Error: update is empty!"));
return fatalFail();
}
for (uint32 i = 0; i < filesCount; ++i) {
QString relativeName;
quint32 fileSize;
QByteArray fileInnerData;
bool executable = false;
stream >> relativeName >> fileSize >> fileInnerData;
#if defined Q_OS_MAC || defined Q_OS_LINUX
stream >> executable;
#endif
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (fileSize != quint32(fileInnerData.size())) {
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
return fatalFail();
}
QFile f(tempDirPath + '/' + relativeName);
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (f.write(fileInnerData) != fileSize) {
f.close();
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
f.close();
if (executable) {
QFileDevice::Permissions p = f.permissions();
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
f.setPermissions(p);
}
}
// create tdata/version file
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
VerChar versionStr[32];
memcpy(versionStr, versionString.c_str(), versionLen);
QFile fVersion(tempDirPath + qsl("/tdata/version"));
if (!fVersion.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
return fatalFail();
}
fVersion.write((const char*)&versionNum, sizeof(VerInt));
fVersion.write((const char*)&versionLen, sizeof(VerInt));
fVersion.write((const char*)&versionStr[0], versionLen);
fVersion.close();
}
if (!tempDir.rename(tempDir.absolutePath(), readyDir.absolutePath())) {
LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'").arg(tempDir.absolutePath()).arg(readyDir.absolutePath()));
return fatalFail();
}
deleteDir(tempDirPath);
outputFile.remove();
emit App::app()->updateReady();
}
PsUpdateDownloader::~PsUpdateDownloader() {
delete reply;
reply = 0;
void psDeleteDir(const QString &dir) {
_removeDirectory(dir);
}
namespace {
@@ -1474,129 +1092,6 @@ int psFixPrevious() {
return 0;
}
#ifdef Q_OS_LINUX
bool moveFile(const char *from, const char *to) {
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
if (!ffrom) {
if (fto) fclose(fto);
return false;
}
if (!fto) {
fclose(ffrom);
return false;
}
static const int BufSize = 65536;
char buf[BufSize];
while (size_t size = fread(buf, 1, BufSize, ffrom)) {
fwrite(buf, 1, size, fto);
}
struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c
//let's say this wont fail since you already worked OK on that fp
if (fstat(fileno(ffrom), &fst) != 0) {
fclose(ffrom);
fclose(fto);
return false;
}
//update to the same uid/gid
if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
fclose(ffrom);
fclose(fto);
return false;
}
//update the permissions
if (fchmod(fileno(fto), fst.st_mode) != 0) {
fclose(ffrom);
fclose(fto);
return false;
}
fclose(ffrom);
fclose(fto);
if (unlink(from)) {
return false;
}
return true;
}
#endif
bool psCheckReadyUpdate() {
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
if (!QDir(readyPath).exists()) {
return false;
}
// check ready version
QString versionPath = readyPath + qsl("/tdata/version");
{
QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
PsUpdateDownloader::clearAll();
return false;
}
VerInt versionNum;
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
PsUpdateDownloader::clearAll();
return false;
}
fVersion.close();
if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
PsUpdateDownloader::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
QString curUpdater = (cExeDir() + "Updater.exe");
QFileInfo updater(cWorkingDir() + "tupdates/ready/Updater.exe");
#elif defined Q_OS_MAC
QString curUpdater = (cExeDir() + "Telegram.app/Contents/Frameworks/Updater");
QFileInfo updater(cWorkingDir() + "tupdates/ready/Telegram.app/Contents/Frameworks/Updater");
#elif defined Q_OS_LINUX
QString curUpdater = (cExeDir() + "Updater");
QFileInfo updater(cWorkingDir() + "tupdates/ready/Updater");
#endif
if (!updater.exists()) {
QFileInfo current(curUpdater);
if (!current.exists()) {
PsUpdateDownloader::clearAll();
return false;
}
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
PsUpdateDownloader::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
PsUpdateDownloader::clearAll();
return false;
}
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
PsUpdateDownloader::clearAll();
return false;
}
#elif defined Q_OS_MAC
QFileInfo to(curUpdater);
QDir().mkpath(to.absolutePath());
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
PsUpdateDownloader::clearAll();
return false;
}
#elif defined Q_OS_LINUX
if (!moveFile(QFile::encodeName(updater.absoluteFilePath()).constData(), QFile::encodeName(curUpdater).constData())) {
PsUpdateDownloader::clearAll();
return false;
}
#endif
return true;
}
void psPostprocessFile(const QString &name) {
}
@@ -1606,7 +1101,7 @@ void psOpenFile(const QString &name, bool openWith) {
void psShowInFolder(const QString &name) {
App::wnd()->layerHidden();
system(("nautilus \"" + QFileInfo(name).absoluteDir().absolutePath() + "\"").toLocal8Bit().constData());
system((qsl("xdg-open ") + escapeShell(QFileInfo(name).absoluteDir().absolutePath())).toUtf8().constData());
}
void psStart() {
@@ -1617,12 +1112,12 @@ void psFinish() {
namespace {
bool _psRunCommand(const QString &command) {
int result = system(command.toLocal8Bit().constData());
int result = system(command.toUtf8().constData());
if (result) {
DEBUG_LOG(("App Error: command failed, code: %1, command: %2").arg(result).arg(command.toLocal8Bit().constData()));
DEBUG_LOG(("App Error: command failed, code: %1, command (in utf8): %2").arg(result).arg(command));
return false;
}
DEBUG_LOG(("App Info: command succeeded, command: %1").arg(command.toLocal8Bit().constData()));
DEBUG_LOG(("App Info: command succeeded, command (in utf8): %1").arg(command));
return true;
}
}
@@ -1655,19 +1150,19 @@ void psRegisterCustomScheme() {
s << "Version=1.0\n";
s << "Name=Telegram Desktop\n";
s << "Comment=Official desktop version of Telegram messaging app\n";
s << "Exec=" << cExeDir().toLocal8Bit().constData() << cExeName().toLocal8Bit().constData() << " -- %u\n";
s << "Icon=" << icon.toLocal8Bit().constData() << "\n";
s << "Exec=" << escapeShell(cExeDir() + cExeName()) << " -- %u\n";
s << "Icon=" << icon << "\n";
s << "Terminal=false\n";
s << "Type=Application\n";
s << "Categories=Network;\n";
s << "MimeType=application/x-xdg-protocol-tg;x-scheme-handler/tg;\n";
f.close();
if (_psRunCommand(qsl("desktop-file-install --dir=%1.local/share/applications --delete-original \"%2\"").arg(home).arg(file))) {
if (_psRunCommand(qsl("desktop-file-install --dir=%1 --delete-original %2").arg(escapeShell(home + qsl(".local/share/applications"))).arg(escapeShell(file)))) {
DEBUG_LOG(("App Info: removing old .desktop file"));
QFile(qsl("%1.local/share/applications/telegram.desktop").arg(home)).remove();
_psRunCommand(qsl("update-desktop-database %1.local/share/applications").arg(home));
_psRunCommand(qsl("update-desktop-database %1").arg(escapeShell(home + qsl(".local/share/applications"))));
_psRunCommand(qsl("xdg-mime default telegramdesktop.desktop x-scheme-handler/tg"));
}
} else {
@@ -1676,7 +1171,7 @@ void psRegisterCustomScheme() {
}
DEBUG_LOG(("App Info: registerting for Gnome"));
if (_psRunCommand(qsl("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command \"%1 -- %s\"").arg(cExeDir() + cExeName()))) {
if (_psRunCommand(qsl("gconftool-2 -t string -s /desktop/gnome/url-handlers/tg/command %1").arg(escapeShell(qsl("%1 -- %s").arg(escapeShell(cExeDir() + cExeName())))))) {
_psRunCommand(qsl("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/needs_terminal false"));
_psRunCommand(qsl("gconftool-2 -t bool -s /desktop/gnome/url-handlers/tg/enabled true"));
}
@@ -1697,7 +1192,7 @@ void psRegisterCustomScheme() {
QTextStream s(&f);
s.setCodec("UTF-8");
s << "[Protocol]\n";
s << "exec=" << cExeDir().toLocal8Bit().constData() << cExeName().toLocal8Bit().constData() << " -- %u\n";
s << "exec=" << escapeShell(cExeDir() + cExeName()) << " -- %u\n";
s << "protocol=tg\n";
s << "input=none\n";
s << "output=none\n";
@@ -1741,7 +1236,7 @@ bool _execUpdater(bool update = true) {
args[argIndex++] = p_datafile;
}
}
QByteArray pathf = cWorkingDir().toLocal8Bit();
QByteArray pathf = cWorkingDir().toUtf8();
if (pathf.size() < MaxLen) {
memcpy(p_pathbuf, pathf.constData(), pathf.size());
args[argIndex++] = p_path;
@@ -1758,8 +1253,7 @@ bool _execUpdater(bool update = true) {
void psExecUpdater() {
if (!_execUpdater()) {
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
PsUpdateDownloader::deleteDir(readyPath);
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
}
@@ -1779,3 +1273,49 @@ void psSendToMenu(bool send, bool silent) {
void psUpdateOverlayed(QWidget *widget) {
}
bool linuxMoveFile(const char *from, const char *to) {
FILE *ffrom = fopen(from, "rb"), *fto = fopen(to, "wb");
if (!ffrom) {
if (fto) fclose(fto);
return false;
}
if (!fto) {
fclose(ffrom);
return false;
}
static const int BufSize = 65536;
char buf[BufSize];
while (size_t size = fread(buf, 1, BufSize, ffrom)) {
fwrite(buf, 1, size, fto);
}
struct stat fst; // from http://stackoverflow.com/questions/5486774/keeping-fileowner-and-permissions-after-copying-file-in-c
//let's say this wont fail since you already worked OK on that fp
if (fstat(fileno(ffrom), &fst) != 0) {
fclose(ffrom);
fclose(fto);
return false;
}
//update to the same uid/gid
if (fchown(fileno(fto), fst.st_uid, fst.st_gid) != 0) {
fclose(ffrom);
fclose(fto);
return false;
}
//update the permissions
if (fchmod(fileno(fto), fst.st_mode) != 0) {
fclose(ffrom);
fclose(fto);
return false;
}
fclose(ffrom);
fclose(fto);
if (unlink(from)) {
return false;
}
return true;
}

View File

@@ -116,55 +116,9 @@ public:
void psInstallEventFilter();
~PsApplication();
signals:
void updateChecking();
void updateLatest();
void updateDownloading(qint64 ready, qint64 total);
void updateReady();
void updateFailed();
};
class PsUpdateDownloader : public QObject {
Q_OBJECT
public:
PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update);
PsUpdateDownloader(QThread *thread, const QString &url);
void unpackUpdate();
int32 ready();
int32 size();
static void deleteDir(const QString &dir);
static void clearAll();
~PsUpdateDownloader();
public slots:
void start();
void partMetaGot();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void sendRequest();
private:
void initOutput();
void fatalFail();
QString updateUrl;
QNetworkAccessManager manager;
QNetworkReply *reply;
int32 already, full;
QFile outputFile;
QMutex mutex;
};
void psDeleteDir(const QString &dir);
void psUserActionDone();
bool psIdleSupported();
@@ -194,7 +148,6 @@ void psBringToBack(QWidget *w);
int psCleanup();
int psFixPrevious();
bool psCheckReadyUpdate();
void psExecUpdater();
void psExecTelegram();
@@ -212,3 +165,5 @@ void psUpdateOverlayed(QWidget *widget);
inline QString psConvertFileUrl(const QString &url) {
return url;
}
bool linuxMoveFile(const char *from, const char *to);

View File

@@ -422,7 +422,7 @@ void PsMainWindow::psMacUpdateMenu() {
canPaste = !App::app()->clipboard()->text().isEmpty();
} else if (FlatTextarea *edit = qobject_cast<FlatTextarea*>(focused)) {
canCut = canCopy = canDelete = edit->textCursor().hasSelection();
canSelectAll = edit->hasText();
canSelectAll = !edit->getLastText().isEmpty();
canUndo = edit->isUndoAvailable();
canRedo = edit->isRedoAvailable();
canPaste = !App::app()->clipboard()->text().isEmpty();
@@ -522,415 +522,10 @@ PsApplication::~PsApplication() {
_psEventFilter = 0;
}
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : reply(0), already(0), full(0) {
updateUrl = qs(update.vurl);
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : reply(0), already(0), full(0) {
updateUrl = url;
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
void PsUpdateDownloader::initOutput() {
QString fileName;
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
if (m.hasMatch()) {
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand());
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
QFileInfo file(fileName);
QDir dir(dirStr);
if (dir.exists()) {
QFileInfoList all = dir.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
if (i->absoluteFilePath() != file.absoluteFilePath()) {
QFile::remove(i->absoluteFilePath());
}
}
} else {
dir.mkdir(dir.absolutePath());
}
outputFile.setFileName(fileName);
if (file.exists()) {
uint64 fullSize = file.size();
if (fullSize < INT_MAX) {
int32 goodSize = (int32)fullSize;
if (goodSize % UpdateChunk) {
goodSize = goodSize - (goodSize % UpdateChunk);
if (goodSize) {
if (outputFile.open(QIODevice::ReadOnly)) {
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
outputFile.close();
if (outputFile.open(QIODevice::WriteOnly)) {
outputFile.write(goodData);
outputFile.close();
QMutexLocker lock(&mutex);
already = goodSize;
}
}
}
} else {
QMutexLocker lock(&mutex);
already = goodSize;
}
}
if (!already) {
QFile::remove(fileName);
}
}
}
void PsUpdateDownloader::start() {
sendRequest();
}
void PsUpdateDownloader::sendRequest() {
QNetworkRequest req(updateUrl);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1);
req.setRawHeader("Range", rangeHeaderValue);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
if (reply) reply->deleteLater();
reply = manager.get(req);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
}
void PsUpdateDownloader::partMetaGot() {
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
Pairs pairs = reply->rawHeaderPairs();
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
if (QString::fromUtf8(i->first).toLower() == "content-range") {
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
if (m.hasMatch()) {
{
QMutexLocker lock(&mutex);
full = m.captured(1).toInt();
}
emit App::app()->updateDownloading(already, full);
}
}
}
}
int32 PsUpdateDownloader::ready() {
QMutexLocker lock(&mutex);
return already;
}
int32 PsUpdateDownloader::size() {
QMutexLocker lock(&mutex);
return full;
}
void PsUpdateDownloader::partFinished(qint64 got, qint64 total) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status != 200 && status != 206 && status != 416) {
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
return fatalFail();
}
}
if (!already && !full) {
QMutexLocker lock(&mutex);
full = total;
}
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
if (!outputFile.isOpen()) {
if (!outputFile.open(QIODevice::Append)) {
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
return fatalFail();
}
}
QByteArray r = reply->readAll();
if (!r.isEmpty()) {
outputFile.write(r);
QMutexLocker lock(&mutex);
already += r.size();
}
if (got >= total) {
reply->deleteLater();
reply = 0;
outputFile.close();
unpackUpdate();
} else {
emit App::app()->updateDownloading(already, full);
}
}
void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
reply = 0;
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status == 416) { // Requested range not satisfiable
outputFile.close();
unpackUpdate();
return;
}
}
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
emit App::app()->updateFailed();
}
void PsUpdateDownloader::deleteDir(const QString &dir) {
void psDeleteDir(const QString &dir) {
objc_deleteDir(dir);
}
void PsUpdateDownloader::fatalFail() {
clearAll();
emit App::app()->updateFailed();
}
void PsUpdateDownloader::clearAll() {
deleteDir(cWorkingDir() + qsl("tupdates"));
}
#ifdef Q_OS_WIN
typedef DWORD VerInt;
typedef WCHAR VerChar;
#else
typedef int VerInt;
typedef wchar_t VerChar;
#endif
void PsUpdateDownloader::unpackUpdate() {
QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
return fatalFail();
}
#ifdef Q_OS_WIN // use Lzma SDK for win
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
#else
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = 0, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hOriginalSizeLen; // header
#endif
QByteArray compressed = outputFile.readAll();
int32 compressedLen = compressed.size() - hSize;
if (compressedLen <= 0) {
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
return fatalFail();
}
outputFile.close();
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready");
deleteDir(tempDirPath);
deleteDir(readyDirPath);
QDir tempDir(tempDirPath), readyDir(readyDirPath);
if (tempDir.exists() || readyDir.exists()) {
LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!"));
return fatalFail();
}
uchar sha1Buffer[20];
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
if (!goodSha1) {
LOG(("Update Error: bad SHA1 hash of update file!"));
return fatalFail();
}
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
if (!pbKey) {
LOG(("Update Error: cant read public rsa key!"));
return fatalFail();
}
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
RSA_free(pbKey);
LOG(("Update Error: bad RSA signature of update file!"));
return fatalFail();
}
RSA_free(pbKey);
QByteArray uncompressed;
int32 uncompressedLen;
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
uncompressed.resize(uncompressedLen);
size_t resultLen = uncompressed.size();
#ifdef Q_OS_WIN // use Lzma SDK for win
SizeT srcLen = compressedLen;
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
if (uncompressRes != SZ_OK) {
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
return fatalFail();
}
#else
lzma_stream stream = LZMA_STREAM_INIT;
lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, LZMA_CONCATENATED);
if (ret != LZMA_OK) {
const char *msg;
switch (ret) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_OPTIONS_ERROR: msg = "Specified preset is not supported"; break;
case LZMA_UNSUPPORTED_CHECK: msg = "Specified integrity check is not supported"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error initializing the decoder: %1 (error code %2)").arg(msg).arg(ret));
return fatalFail();
}
stream.avail_in = compressedLen;
stream.next_in = (uint8_t*)(compressed.constData() + hSize);
stream.avail_out = resultLen;
stream.next_out = (uint8_t*)uncompressed.data();
lzma_ret res = lzma_code(&stream, LZMA_FINISH);
if (stream.avail_in) {
LOG(("Error in decompression, %1 bytes left in _in of %2 whole.").arg(stream.avail_in).arg(compressedLen));
return fatalFail();
} else if (stream.avail_out) {
LOG(("Error in decompression, %1 bytes free left in _out of %2 whole.").arg(stream.avail_out).arg(resultLen));
return fatalFail();
}
lzma_end(&stream);
if (res != LZMA_OK && res != LZMA_STREAM_END) {
const char *msg;
switch (res) {
case LZMA_MEM_ERROR: msg = "Memory allocation failed"; break;
case LZMA_FORMAT_ERROR: msg = "The input data is not in the .xz format"; break;
case LZMA_OPTIONS_ERROR: msg = "Unsupported compression options"; break;
case LZMA_DATA_ERROR: msg = "Compressed file is corrupt"; break;
case LZMA_BUF_ERROR: msg = "Compressed data is truncated or otherwise corrupt"; break;
default: msg = "Unknown error, possibly a bug"; break;
}
LOG(("Error in decompression: %1 (error code %2)").arg(msg).arg(res));
return fatalFail();
}
#endif
tempDir.mkdir(tempDir.absolutePath());
quint32 version;
{
QBuffer buffer(&uncompressed);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
stream >> version;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (version <= AppVersion) {
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
return fatalFail();
}
quint32 filesCount;
stream >> filesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (!filesCount) {
LOG(("Update Error: update is empty!"));
return fatalFail();
}
for (uint32 i = 0; i < filesCount; ++i) {
QString relativeName;
quint32 fileSize;
QByteArray fileInnerData;
bool executable = false;
stream >> relativeName >> fileSize >> fileInnerData;
#if defined Q_OS_MAC || defined Q_OS_LINUX
stream >> executable;
#endif
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (fileSize != quint32(fileInnerData.size())) {
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
return fatalFail();
}
QFile f(tempDirPath + '/' + relativeName);
if (!QDir().mkpath(QFileInfo(f).absolutePath())) {
LOG(("Update Error: cant mkpath for file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (f.write(fileInnerData) != fileSize) {
f.close();
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
f.close();
if (executable) {
QFileDevice::Permissions p = f.permissions();
p |= QFileDevice::ExeOwner | QFileDevice::ExeUser | QFileDevice::ExeGroup | QFileDevice::ExeOther;
f.setPermissions(p);
}
}
// create tdata/version file
tempDir.mkdir(QDir(tempDirPath + qsl("/tdata")).absolutePath());
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
VerInt versionNum = VerInt(version), versionLen = VerInt(versionString.size() * sizeof(VerChar));
VerChar versionStr[32];
memcpy(versionStr, versionString.c_str(), versionLen);
QFile fVersion(tempDirPath + qsl("/tdata/version"));
if (!fVersion.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/version")));
return fatalFail();
}
fVersion.write((const char*)&versionNum, sizeof(VerInt));
fVersion.write((const char*)&versionLen, sizeof(VerInt));
fVersion.write((const char*)&versionStr[0], versionLen);
fVersion.close();
}
if (!tempDir.rename(tempDir.absolutePath(), readyDir.absolutePath())) {
LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'").arg(tempDir.absolutePath()).arg(readyDir.absolutePath()));
return fatalFail();
}
deleteDir(tempDirPath);
outputFile.remove();
emit App::app()->updateReady();
}
PsUpdateDownloader::~PsUpdateDownloader() {
delete reply;
reply = 0;
}
namespace {
uint64 _lastUserAction = 0;
}
@@ -1029,73 +624,6 @@ int psFixPrevious() {
return 0;
}
bool psCheckReadyUpdate() {
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
if (!QDir(readyPath).exists()) {
return false;
}
// check ready version
QString versionPath = readyPath + qsl("/tdata/version");
{
QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
PsUpdateDownloader::clearAll();
return false;
}
VerInt versionNum;
if (fVersion.read((char*)&versionNum, sizeof(VerInt)) != sizeof(VerInt)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
PsUpdateDownloader::clearAll();
return false;
}
fVersion.close();
if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
PsUpdateDownloader::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Updater.exe"));
#elif defined Q_OS_MAC
QString curUpdater = (cExeDir() + cExeName() + qsl("/Contents/Frameworks/Updater"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Telegram.app/Contents/Frameworks/Updater"));
#endif
if (!updater.exists()) {
QFileInfo current(curUpdater);
if (!current.exists()) {
PsUpdateDownloader::clearAll();
return false;
}
if (!QFile(current.absoluteFilePath()).copy(updater.absoluteFilePath())) {
PsUpdateDownloader::clearAll();
return false;
}
}
#ifdef Q_OS_WIN
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
PsUpdateDownloader::clearAll();
return false;
}
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
PsUpdateDownloader::clearAll();
return false;
}
#elif defined Q_OS_MAC
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater));
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
PsUpdateDownloader::clearAll();
return false;
}
#endif
return true;
}
bool psShowOpenWithMenu(int x, int y, const QString &file) {
return objc_showOpenWithMenu(x, y, file);
}
@@ -1125,8 +653,7 @@ void psRegisterCustomScheme() {
void psExecUpdater() {
if (!objc_execUpdater()) {
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
PsUpdateDownloader::deleteDir(readyPath);
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
}

View File

@@ -144,55 +144,9 @@ public:
void psInstallEventFilter();
~PsApplication();
signals:
void updateChecking();
void updateLatest();
void updateDownloading(qint64 ready, qint64 total);
void updateReady();
void updateFailed();
};
class PsUpdateDownloader : public QObject {
Q_OBJECT
public:
PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update);
PsUpdateDownloader(QThread *thread, const QString &url);
void unpackUpdate();
int32 ready();
int32 size();
static void deleteDir(const QString &dir);
static void clearAll();
~PsUpdateDownloader();
public slots:
void start();
void partMetaGot();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void sendRequest();
private:
void initOutput();
void fatalFail();
QString updateUrl;
QNetworkAccessManager manager;
QNetworkReply *reply;
int32 already, full;
QFile outputFile;
QMutex mutex;
};
void psDeleteDir(const QString &dir);
void psUserActionDone();
bool psIdleSupported();
@@ -222,7 +176,6 @@ void psBringToBack(QWidget *w);
int psCleanup();
int psFixPrevious();
bool psCheckReadyUpdate();
void psExecUpdater();
void psExecTelegram();

View File

@@ -1061,10 +1061,6 @@ void PsMainWindow::psInitSize() {
setMinimumHeight(st::wndMinHeight);
TWindowPos pos(cWindowPos());
if (cDebug()) { // temp while design
pos.w = st::wndDefWidth;
pos.h = st::wndDefHeight;
}
QRect avail(App::app() ? App::app()->desktop()->availableGeometry() : QDesktopWidget().availableGeometry());
bool maximized = false;
QRect geom(avail.x() + (avail.width() - st::wndDefWidth) / 2, avail.y() + (avail.height() - st::wndDefHeight) / 2, st::wndDefWidth, st::wndDefHeight);
@@ -1389,184 +1385,7 @@ PsApplication::~PsApplication() {
_psEventFilter = 0;
}
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update) : already(0), reply(0), full(0) {
updateUrl = qs(update.vurl);
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
PsUpdateDownloader::PsUpdateDownloader(QThread *thread, const QString &url) : already(0), reply(0), full(0) {
updateUrl = url;
moveToThread(thread);
manager.moveToThread(thread);
App::setProxySettings(manager);
connect(thread, SIGNAL(started()), this, SLOT(start()));
initOutput();
}
void PsUpdateDownloader::initOutput() {
QString fileName;
QRegularExpressionMatch m = QRegularExpression(qsl("/([^/\\?]+)(\\?|$)")).match(updateUrl);
if (m.hasMatch()) {
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
}
if (fileName.isEmpty()) {
fileName = qsl("tupdate-%1").arg(rand());
}
QString dirStr = cWorkingDir() + qsl("tupdates/");
fileName = dirStr + fileName;
QFileInfo file(fileName);
QDir dir(dirStr);
if (dir.exists()) {
QFileInfoList all = dir.entryInfoList(QDir::Files);
for (QFileInfoList::iterator i = all.begin(), e = all.end(); i != e; ++i) {
if (i->absoluteFilePath() != file.absoluteFilePath()) {
QFile::remove(i->absoluteFilePath());
}
}
} else {
dir.mkdir(dir.absolutePath());
}
outputFile.setFileName(fileName);
if (file.exists()) {
uint64 fullSize = file.size();
if (fullSize < INT_MAX) {
int32 goodSize = (int32)fullSize;
if (goodSize % UpdateChunk) {
goodSize = goodSize - (goodSize % UpdateChunk);
if (goodSize) {
if (outputFile.open(QIODevice::ReadOnly)) {
QByteArray goodData = outputFile.readAll().mid(0, goodSize);
outputFile.close();
if (outputFile.open(QIODevice::WriteOnly)) {
outputFile.write(goodData);
outputFile.close();
QMutexLocker lock(&mutex);
already = goodSize;
}
}
}
} else {
QMutexLocker lock(&mutex);
already = goodSize;
}
}
if (!already) {
QFile::remove(fileName);
}
}
}
void PsUpdateDownloader::start() {
sendRequest();
}
void PsUpdateDownloader::sendRequest() {
QNetworkRequest req(updateUrl);
QByteArray rangeHeaderValue = "bytes=" + QByteArray::number(already) + "-";// + QByteArray::number(already + cUpdateChunk() - 1);
req.setRawHeader("Range", rangeHeaderValue);
req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
if (reply) reply->deleteLater();
reply = manager.get(req);
connect(reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(partFinished(qint64,qint64)));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(partFailed(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(metaDataChanged()), this, SLOT(partMetaGot()));
}
void PsUpdateDownloader::partMetaGot() {
typedef QList<QNetworkReply::RawHeaderPair> Pairs;
Pairs pairs = reply->rawHeaderPairs();
for (Pairs::iterator i = pairs.begin(), e = pairs.end(); i != e; ++i) {
if (QString::fromUtf8(i->first).toLower() == "content-range") {
QRegularExpressionMatch m = QRegularExpression(qsl("/(\\d+)([^\\d]|$)")).match(QString::fromUtf8(i->second));
if (m.hasMatch()) {
{
QMutexLocker lock(&mutex);
full = m.captured(1).toInt();
}
emit App::app()->updateDownloading(already, full);
}
}
}
}
int32 PsUpdateDownloader::ready() {
QMutexLocker lock(&mutex);
return already;
}
int32 PsUpdateDownloader::size() {
QMutexLocker lock(&mutex);
return full;
}
void PsUpdateDownloader::partFinished(qint64 got, qint64 total) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status != 200 && status != 206 && status != 416) {
LOG(("Update Error: Bad HTTP status received in partFinished(): %1").arg(status));
return fatalFail();
}
}
if (!already && !full) {
QMutexLocker lock(&mutex);
full = total;
}
DEBUG_LOG(("Update Info: part %1 of %2").arg(got).arg(total));
if (!outputFile.isOpen()) {
if (!outputFile.open(QIODevice::Append)) {
LOG(("Update Error: Could not open output file '%1' for appending").arg(outputFile.fileName()));
return fatalFail();
}
}
QByteArray r = reply->readAll();
if (!r.isEmpty()) {
outputFile.write(r);
QMutexLocker lock(&mutex);
already += r.size();
}
if (got >= total) {
reply->deleteLater();
reply = 0;
outputFile.close();
unpackUpdate();
} else {
emit App::app()->updateDownloading(already, full);
}
}
void PsUpdateDownloader::partFailed(QNetworkReply::NetworkError e) {
if (!reply) return;
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
reply = 0;
if (statusCode.isValid()) {
int status = statusCode.toInt();
if (status == 416) { // Requested range not satisfiable
outputFile.close();
unpackUpdate();
return;
}
}
LOG(("Update Error: failed to download part starting from %1, error %2").arg(already).arg(e));
emit App::app()->updateFailed();
}
void PsUpdateDownloader::deleteDir(const QString &dir) {
void psDeleteDir(const QString &dir) {
std::wstring wDir = QDir::toNativeSeparators(dir).toStdWString();
WCHAR path[4096];
memcpy(path, wDir.c_str(), (wDir.size() + 1) * sizeof(WCHAR));
@@ -1586,185 +1405,6 @@ void PsUpdateDownloader::deleteDir(const QString &dir) {
int res = SHFileOperation(&file_op);
}
void PsUpdateDownloader::fatalFail() {
clearAll();
emit App::app()->updateFailed();
}
void PsUpdateDownloader::clearAll() {
deleteDir(cWorkingDir() + qsl("tupdates"));
}
QString winapiErrorWrap() {
WCHAR errMsg[2048];
DWORD errorCode = GetLastError();
LPTSTR errorText = NULL, errorTextDefault = L"(Unknown error)";
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorText, 0, 0);
if (!errorText) {
errorText = errorTextDefault;
}
StringCbPrintf(errMsg, sizeof(errMsg), L"Error code: %d, error message: %s", errorCode, errorText);
if (errorText != errorTextDefault) {
LocalFree(errorText);
}
return QString::fromWCharArray(errMsg);
}
void PsUpdateDownloader::unpackUpdate() {
QByteArray packed;
if (!outputFile.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read updates file!"));
return fatalFail();
}
const int32 hSigLen = 128, hShaLen = 20, hPropsLen = LZMA_PROPS_SIZE, hOriginalSizeLen = sizeof(int32), hSize = hSigLen + hShaLen + hPropsLen + hOriginalSizeLen; // header
QByteArray compressed = outputFile.readAll();
int32 compressedLen = compressed.size() - hSize;
if (compressedLen <= 0) {
LOG(("Update Error: bad compressed size: %1").arg(compressed.size()));
return fatalFail();
}
outputFile.close();
QString tempDirPath = cWorkingDir() + qsl("tupdates/temp"), readyDirPath = cWorkingDir() + qsl("tupdates/ready");
deleteDir(tempDirPath);
deleteDir(readyDirPath);
{
QDir tempDir(tempDirPath), readyDir(readyDirPath);
if (tempDir.exists() || readyDir.exists()) {
LOG(("Update Error: cant clear tupdates/temp or tupdates/ready dir!"));
return fatalFail();
}
tempDirPath = tempDir.absolutePath();
readyDirPath = readyDir.absolutePath();
uchar sha1Buffer[20];
bool goodSha1 = !memcmp(compressed.constData() + hSigLen, hashSha1(compressed.constData() + hSigLen + hShaLen, compressedLen + hPropsLen + hOriginalSizeLen, sha1Buffer), hShaLen);
if (!goodSha1) {
LOG(("Update Error: bad SHA1 hash of update file!"));
return fatalFail();
}
RSA *pbKey = PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(DevChannel ? UpdatesPublicDevKey : UpdatesPublicKey), -1), 0, 0, 0);
if (!pbKey) {
LOG(("Update Error: cant read public rsa key!"));
return fatalFail();
}
if (RSA_verify(NID_sha1, (const uchar*)(compressed.constData() + hSigLen), hShaLen, (const uchar*)(compressed.constData()), hSigLen, pbKey) != 1) { // verify signature
RSA_free(pbKey);
LOG(("Update Error: bad RSA signature of update file!"));
return fatalFail();
}
RSA_free(pbKey);
QByteArray uncompressed;
int32 uncompressedLen;
memcpy(&uncompressedLen, compressed.constData() + hSigLen + hShaLen + hPropsLen, hOriginalSizeLen);
uncompressed.resize(uncompressedLen);
size_t resultLen = uncompressed.size();
SizeT srcLen = compressedLen;
int uncompressRes = LzmaUncompress((uchar*)uncompressed.data(), &resultLen, (const uchar*)(compressed.constData() + hSize), &srcLen, (const uchar*)(compressed.constData() + hSigLen + hShaLen), LZMA_PROPS_SIZE);
if (uncompressRes != SZ_OK) {
LOG(("Update Error: could not uncompress lzma, code: %1").arg(uncompressRes));
return fatalFail();
}
QDir().mkdir(tempDirPath);
quint32 version;
QBuffer buffer(&uncompressed);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
stream >> version;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read version from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (version <= AppVersion) {
LOG(("Update Error: downloaded version %1 is not greater, than mine %2").arg(version).arg(AppVersion));
return fatalFail();
}
quint32 filesCount;
stream >> filesCount;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read files count from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (!filesCount) {
LOG(("Update Error: update is empty!"));
return fatalFail();
}
for (int32 i = 0; i < filesCount; ++i) {
QString relativeName;
quint32 fileSize;
QByteArray fileInnerData;
stream >> relativeName >> fileSize >> fileInnerData;
if (stream.status() != QDataStream::Ok) {
LOG(("Update Error: cant read file from downloaded stream, status: %1").arg(stream.status()));
return fatalFail();
}
if (fileSize != fileInnerData.size()) {
LOG(("Update Error: bad file size %1 not matching data size %2").arg(fileSize).arg(fileInnerData.size()));
return fatalFail();
}
QFile f(tempDirPath + '/' + relativeName);
if (!f.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant open file '%1' for writing").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
if (f.write(fileInnerData) != fileSize) {
f.close();
LOG(("Update Error: cant write file '%1'").arg(tempDirPath + '/' + relativeName));
return fatalFail();
}
f.close();
}
// create tdata/version file
QDir().mkdir(tempDirPath + qsl("/tdata"));
std::wstring versionString = ((version % 1000) ? QString("%1.%2.%3").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000)).arg(int(version % 1000)) : QString("%1.%2").arg(int(version / 1000000)).arg(int((version % 1000000) / 1000))).toStdWString();
DWORD versionNum = DWORD(version), versionLen = DWORD(versionString.size() * sizeof(WCHAR));
WCHAR versionStr[32];
memcpy(versionStr, versionString.c_str(), versionLen);
QFile fVersion(tempDirPath + qsl("/tdata/version"));
if (!fVersion.open(QIODevice::WriteOnly)) {
LOG(("Update Error: cant write version file '%1'").arg(tempDirPath + qsl("/tdata/version")));
return fatalFail();
}
fVersion.write((const char*)&versionNum, sizeof(DWORD));
fVersion.write((const char*)&versionLen, sizeof(DWORD));
fVersion.write((const char*)&versionStr[0], versionLen);
fVersion.close();
}
std::wstring tempDirNative = QDir::toNativeSeparators(tempDirPath).toStdWString(), readyDirNative = QDir::toNativeSeparators(readyDirPath).toStdWString();
if (!MoveFile(tempDirNative.c_str(), readyDirNative.c_str())) {
LOG(("Update Error: cant rename temp dir '%1' to ready dir '%2'. %3").arg(QString::fromStdWString(tempDirNative)).arg(QString::fromStdWString(readyDirNative)).arg(winapiErrorWrap()));
return fatalFail();
}
deleteDir(tempDirPath);
outputFile.remove();
emit App::app()->updateReady();
}
PsUpdateDownloader::~PsUpdateDownloader() {
delete reply;
reply = 0;
}
namespace {
BOOL CALLBACK _ActivateProcess(HWND hWnd, LPARAM lParam) {
uint64 &processId(*(uint64*)lParam);
@@ -2138,65 +1778,6 @@ int psFixPrevious() {
return 0;
}
bool psCheckReadyUpdate() {
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
if (!QDir(readyPath).exists()) {
return false;
}
// check ready version
QString versionPath = readyPath + qsl("/tdata/version");
{
QFile fVersion(versionPath);
if (!fVersion.open(QIODevice::ReadOnly)) {
LOG(("Update Error: cant read version file '%1'").arg(versionPath));
PsUpdateDownloader::clearAll();
return false;
}
DWORD versionNum;
if (fVersion.read((char*)&versionNum, sizeof(DWORD)) != sizeof(DWORD)) {
LOG(("Update Error: cant read version from file '%1'").arg(versionPath));
PsUpdateDownloader::clearAll();
return false;
}
fVersion.close();
if (versionNum <= AppVersion) {
LOG(("Update Error: cant install version %1 having version %2").arg(versionNum).arg(AppVersion));
PsUpdateDownloader::clearAll();
return false;
}
}
QString curUpdater = (cExeDir() + qsl("Updater.exe"));
QFileInfo updater(cWorkingDir() + qsl("tupdates/ready/Updater.exe"));
if (!updater.exists()) {
QFileInfo current(curUpdater);
if (!current.exists()) {
PsUpdateDownloader::clearAll();
return false;
}
if (CopyFile(current.absoluteFilePath().toStdWString().c_str(), updater.absoluteFilePath().toStdWString().c_str(), TRUE) == FALSE) {
PsUpdateDownloader::clearAll();
return false;
}
}
if (CopyFile(updater.absoluteFilePath().toStdWString().c_str(), curUpdater.toStdWString().c_str(), FALSE) == FALSE) {
DWORD errorCode = GetLastError();
if (errorCode == ERROR_ACCESS_DENIED) { // we are in write-protected dir, like Program Files
cSetWriteProtected(true);
return true;
} else {
PsUpdateDownloader::clearAll();
return false;
}
}
if (DeleteFile(updater.absoluteFilePath().toStdWString().c_str()) == FALSE) {
PsUpdateDownloader::clearAll();
return false;
}
return true;
}
void psPostprocessFile(const QString &name) {
std::wstring zoneFile = QDir::toNativeSeparators(name).toStdWString() + L":Zone.Identifier";
HANDLE f = CreateFile(zoneFile.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
@@ -2478,7 +2059,7 @@ void psExecUpdater() {
if (cStartInTray()) targs += qsl(" -startintray");
if (cWriteProtected()) targs += qsl(" -writeprotected \"") + cExeDir() + '"';
QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/ready/Updater.exe")) : (cExeDir() + qsl("Updater.exe"));
QString updaterPath = cWriteProtected() ? (cWorkingDir() + qsl("tupdates/temp/Updater.exe")) : (cExeDir() + qsl("Updater.exe"));
QString updater(QDir::toNativeSeparators(updaterPath)), wdir(QDir::toNativeSeparators(cWorkingDir()));
@@ -2486,8 +2067,7 @@ void psExecUpdater() {
HINSTANCE r = ShellExecute(0, cWriteProtected() ? L"runas" : 0, updater.toStdWString().c_str(), targs.toStdWString().c_str(), wdir.isEmpty() ? 0 : wdir.toStdWString().c_str(), SW_SHOWNORMAL);
if (long(r) < 32) {
DEBUG_LOG(("Application Error: failed to execute %1, working directory: '%2', result: %3").arg(updater).arg(wdir).arg(long(r)));
QString readyPath = cWorkingDir() + qsl("tupdates/ready");
PsUpdateDownloader::deleteDir(readyPath);
psDeleteDir(cWorkingDir() + qsl("tupdates/temp"));
}
}

View File

@@ -117,55 +117,9 @@ public:
void psInstallEventFilter();
~PsApplication();
signals:
void updateChecking();
void updateLatest();
void updateDownloading(qint64 ready, qint64 total);
void updateReady();
void updateFailed();
};
class PsUpdateDownloader : public QObject {
Q_OBJECT
public:
PsUpdateDownloader(QThread *thread, const MTPDhelp_appUpdate &update);
PsUpdateDownloader(QThread *thread, const QString &url);
void unpackUpdate();
int32 ready();
int32 size();
static void deleteDir(const QString &dir);
static void clearAll();
~PsUpdateDownloader();
public slots:
void start();
void partMetaGot();
void partFinished(qint64 got, qint64 total);
void partFailed(QNetworkReply::NetworkError e);
void sendRequest();
private:
void initOutput();
void fatalFail();
QString updateUrl;
QNetworkAccessManager manager;
QNetworkReply *reply;
int32 already, full;
QFile outputFile;
QMutex mutex;
};
void psDeleteDir(const QString &dir);
void psUserActionDone();
bool psIdleSupported();
@@ -196,7 +150,6 @@ void psBringToBack(QWidget *w);
int psCleanup();
int psFixPrevious();
bool psCheckReadyUpdate();
void psExecUpdater();
void psExecTelegram();

View File

@@ -84,6 +84,9 @@ QString gTimeFormat = qsl("hh:mm");
int32 gAutoLock = 3600;
bool gHasPasscode = false;
bool gHasAudioPlayer = true;
bool gHasAudioCapture = true;
DBIEmojiTab gEmojiTab = dbietRecent;
RecentEmojiPack gRecentEmojis;
RecentEmojisPreload gRecentEmojisPreload;
@@ -136,6 +139,7 @@ QUrl gUpdateURL = QUrl(qsl("http://tdesktop.com/linux/tupdates/current"));
#endif
bool gContactsReceived = false;
bool gDialogsReceived = false;
bool gWideMode = true;

View File

@@ -53,11 +53,11 @@ inline bool rtl() {
}
struct mtpDcOption {
mtpDcOption(int _id, const string &_host, const string &_ip, int _port) : id(_id), host(_host), ip(_ip), port(_port) {
mtpDcOption(int id, int flags, const string &ip, int port) : id(id), flags(flags), ip(ip), port(port) {
}
int id;
string host;
int flags;
string ip;
int port;
};
@@ -134,6 +134,9 @@ DeclareSetting(QString, TimeFormat);
DeclareSetting(int32, AutoLock);
DeclareSetting(bool, HasPasscode);
DeclareSetting(bool, HasAudioPlayer);
DeclareSetting(bool, HasAudioCapture);
inline void cChangeTimeFormat(const QString &newFormat) {
if (!newFormat.isEmpty()) cSetTimeFormat(newFormat);
}
@@ -287,6 +290,7 @@ DeclareReadSetting(DBIPlatform, Platform);
DeclareReadSetting(QUrl, UpdateURL);
DeclareSetting(bool, ContactsReceived);
DeclareSetting(bool, DialogsReceived);
DeclareSetting(bool, WideMode);

View File

@@ -37,6 +37,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "langloaderplain.h"
#include "gui/filedialog.h"
#include "autoupdater.h"
#include "localstorage.h"
Slider::Slider(QWidget *parent, const style::slider &st, int32 count, int32 sel) : QWidget(parent),
@@ -1172,7 +1174,7 @@ void SettingsInner::onCheckNow() {
}
void SettingsInner::onRestartNow() {
psCheckReadyUpdate();
checkReadyUpdate();
if (_updatingState == UpdatingReady) {
cSetRestartingUpdate(true);
} else {

View File

@@ -22,7 +22,6 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
Q_IMPORT_PLUGIN(QWebpPlugin)
#elif defined Q_OS_MAC
Q_IMPORT_PLUGIN(QCoreWlanEnginePlugin)
Q_IMPORT_PLUGIN(QGenericEnginePlugin)
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
Q_IMPORT_PLUGIN(QDDSPlugin)

View File

@@ -201,10 +201,73 @@ void UserData::setPhone(const QString &newPhone) {
++nameVersion;
}
void UserData::setBotInfoVersion(int32 version) {
if (version < 0) {
delete botInfo;
botInfo = 0;
} else if (!botInfo) {
botInfo = new BotInfo();
botInfo->version = version;
} else if (botInfo->version < version) {
botInfo->commands.clear();
botInfo->description.clear();
botInfo->shareText.clear();
botInfo->version = version;
botInfo->inited = false;
}
}
void UserData::setBotInfo(const MTPBotInfo &info) {
switch (info.type()) {
case mtpc_botInfoEmpty:
delete botInfo;
botInfo = 0;
break;
case mtpc_botInfo: {
const MTPDbotInfo &d(info.c_botInfo());
if (App::peerFromUser(d.vuser_id.v) != id) return;
if (botInfo) {
botInfo->version = d.vversion.v;
} else {
setBotInfoVersion(d.vversion.v);
}
QString desc = qs(d.vdescription);
if (botInfo->description != desc) {
botInfo->description = desc;
botInfo->text = Text(st::msgMinWidth);
}
botInfo->shareText = qs(d.vshare_text);
const QVector<MTPBotCommand> &v(d.vcommands.c_vector().v);
botInfo->commands.clear();
botInfo->commands.reserve(v.size());
for (int32 i = 0, l = v.size(); i < l; ++i) {
if (v.at(i).type() == mtpc_botCommand) {
botInfo->commands.push_back(BotCommand(qs(v.at(i).c_botCommand().vcommand), qs(v.at(i).c_botCommand().vparams), qs(v.at(i).c_botCommand().vdescription)));
}
}
botInfo->inited = true;
} break;
}
}
void UserData::nameUpdated() {
nameText.setText(st::msgNameFont, name, _textNameOptions);
}
void UserData::madeAction() {
int32 t = unixtime();
if (onlineTill <= 0 && -onlineTill < t) {
onlineTill = -t - SetOnlineAfterActivity;
if (App::main()) App::main()->peerUpdated(this);
} else if (onlineTill > 0 && onlineTill < t + 1) {
onlineTill = t + SetOnlineAfterActivity;
if (App::main()) App::main()->peerUpdated(this);
}
}
void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) {
switch (p.type()) {
case mtpc_chatPhoto: {
@@ -303,7 +366,7 @@ void VideoOpenLink::onClick(Qt::MouseButton button) const {
QString filename = saveFileName(lang(lng_save_video), qsl("MOV Video (*.mov);;All files (*.*)"), qsl("video"), qsl(".mov"), false);
if (!filename.isEmpty()) {
data->openOnSave = 1;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
data->save(filename);
}
}
@@ -369,19 +432,17 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const {
AudioData *data = audio();
if ((!data->user && !data->date) || button != Qt::LeftButton) return;
bool mp3 = (data->mime == QLatin1String("audio/mp3"));
QString already = data->already(true);
bool play = !mp3 && audioVoice();
bool play = audioPlayer();
if (!already.isEmpty() || (!data->data.isEmpty() && play)) {
if (play) {
AudioData *playing = 0;
VoiceMessageState playingState = VoiceMessageStopped;
audioVoice()->currentState(&playing, &playingState);
if (playing == data && playingState != VoiceMessageStopped) {
audioVoice()->pauseresume();
AudioPlayerState playingState = AudioPlayerStopped;
audioPlayer()->currentState(&playing, &playingState);
if (playing == data && playingState != AudioPlayerStopped) {
audioPlayer()->pauseresume();
} else {
audioVoice()->play(data);
audioPlayer()->play(data);
if (App::main()) App::main()->audioMarkRead(data);
}
} else {
@@ -393,10 +454,11 @@ void AudioOpenLink::onClick(Qt::MouseButton button) const {
if (data->status != FileReady) return;
bool mp3 = (data->mime == QLatin1String("audio/mp3"));
QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false);
if (!filename.isEmpty()) {
data->openOnSave = 1;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
data->save(filename);
}
}
@@ -421,7 +483,7 @@ void AudioSaveLink::doSave(AudioData *data, bool forceSavingAs) {
data->cancel();
} else if (!already.isEmpty()) {
data->openOnSave = -1;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
}
data->save(filename);
}
@@ -470,8 +532,10 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
if (reader.canRead()) {
if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem()) {
startGif(App::hoveredLinkItem(), already);
} else if (App::hoveredLinkItem() || App::contextItem()) {
App::wnd()->showDocument(data, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem());
} else {
App::wnd()->showDocument(data, App::hoveredLinkItem());
psOpenFile(already);
}
} else {
psOpenFile(already);
@@ -501,7 +565,7 @@ void DocumentOpenLink::onClick(Qt::MouseButton button) const {
QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false);
if (!filename.isEmpty()) {
data->openOnSave = 1;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
data->save(filename);
}
}
@@ -538,7 +602,7 @@ void DocumentSaveLink::doSave(DocumentData *data, bool forceSavingAs) {
data->cancel();
} else if (!already.isEmpty()) {
data->openOnSave = -1;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : 0;
data->openOnSaveMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->id : (App::contextItem() ? App::contextItem()->id : 0);
}
data->save(filename);
}

View File

@@ -118,15 +118,37 @@ private:
PeerData *_peer;
};
struct BotCommand {
BotCommand(const QString &command, const QString &params, const QString &description) : command(command), params(params), description(description) {
}
QString command, params, description;
};
struct BotInfo {
BotInfo() : inited(false), readsAllHistory(false), cantJoinGroups(false), version(0), text(st::msgMinWidth) {
}
bool inited;
bool readsAllHistory, cantJoinGroups;
int32 version;
QString shareText, description;
QList<BotCommand> commands;
Text text; // description
QString startToken, startGroupToken;
};
struct PhotoData;
struct UserData : public PeerData {
UserData(const PeerId &id) : PeerData(id), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1) {
UserData(const PeerId &id) : PeerData(id), photoId(0), lnk(new PeerLink(this)), onlineTill(0), contact(-1), photosCount(-1), botInfo(0) {
}
void setPhoto(const MTPUserProfilePhoto &photo);
void setName(const QString &first, const QString &last, const QString &phoneName, const QString &username);
void setPhone(const QString &newPhone);
void setBotInfoVersion(int32 version);
void setBotInfo(const MTPBotInfo &info);
void nameUpdated();
void madeAction(); // pseudo-online
QString firstName;
QString lastName;
QString username;
@@ -140,10 +162,12 @@ struct UserData : public PeerData {
typedef QList<PhotoData*> Photos;
Photos photos;
int32 photosCount; // -1 not loaded, 0 all loaded
BotInfo *botInfo;
};
struct ChatData : public PeerData {
ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), photoId(0) {
ChatData(const PeerId &id) : PeerData(id), count(0), date(0), version(0), left(false), forbidden(true), botStatus(0), photoId(0) {
}
void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = 0);
int32 count;
@@ -158,12 +182,19 @@ struct ChatData : public PeerData {
CanKick cankick;
typedef QList<UserData*> LastAuthors;
LastAuthors lastAuthors;
typedef QMap<UserData*, bool> MarkupSenders;
MarkupSenders markupSenders;
int32 botStatus; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other
ImagePtr photoFull;
PhotoId photoId;
QString invitationUrl;
// geo
};
inline int32 newMessageFlags(PeerData *p) {
return (p->input.type() == mtpc_inputPeerSelf) ? 0 : (((p->chat || !p->asUser()->botInfo) ? MTPDmessage_flag_unread : 0) | MTPDmessage_flag_out);
}
typedef QMap<char, QPixmap> PreparedPhotoThumbs;
struct PhotoData {
PhotoData(const PhotoId &id, const uint64 &access = 0, int32 user = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()) :

View File

@@ -23,6 +23,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
#include "passcodewidget.h"
#include "window.h"
#include "application.h"
#include "autoupdater.h"
SysBtn::SysBtn(QWidget *parent, const style::sysButton &st, const QString &text) : Button(parent),
_st(st), a_color(_st.color->c), _overLevel(0), _text(text) {
@@ -142,7 +143,7 @@ UpdateBtn::UpdateBtn(QWidget *parent, Window *window, const QString &text) : Sys
}
void UpdateBtn::onClick() {
psCheckReadyUpdate();
checkReadyUpdate();
if (App::app()->updatingState() == Application::UpdatingReady) {
cSetRestartingUpdate(true);
} else {

View File

@@ -233,7 +233,7 @@ QString rusKeyboardLayoutSwitch(const QString &from);
enum DataBlockId {
dbiKey = 0x00,
dbiUser = 0x01,
dbiDcOption = 0x02,
dbiDcOptionOld = 0x02,
dbiMaxGroupCount = 0x03,
dbiMutePeer = 0x04,
dbiSendKey = 0x05,
@@ -270,6 +270,7 @@ enum DataBlockId {
dbiRecentEmojis = 0x24,
dbiEmojiVariants = 0x25,
dbiRecentStickers = 0x26,
dbiDcOption = 0x27,
dbiEncryptedWithSalt = 333,
dbiEncrypted = 444,
@@ -326,6 +327,8 @@ enum DBIScale {
dbisScaleCount = 5,
};
static const int MatrixRowShift = 40000;
enum DBIEmojiTab {
dbietRecent = -1,
dbietPeople = 0,
@@ -338,7 +341,6 @@ enum DBIEmojiTab {
dbietStickers = 666,
};
static const int emojiTabCount = 8;
static const int emojiTabShift = 100000;
inline DBIEmojiTab emojiTabAtIndex(int index) {
return (index < 0 || index >= emojiTabCount) ? dbietRecent : DBIEmojiTab(index - 1);
}

View File

@@ -57,7 +57,7 @@ void ConnectingWidget::set(const QString &text, const QString &reconnect) {
void ConnectingWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
_shadow.paint(p, QRect(0, st::boxShadow.pxHeight(), width() - st::boxShadow.pxWidth(), height() - st::boxShadow.pxHeight()), QPoint(0, 0), BoxShadow::Top | BoxShadow::Right);
_shadow.paint(p, QRect(0, st::boxShadow.pxHeight(), width() - st::boxShadow.pxWidth(), height() - st::boxShadow.pxHeight()), 0, BoxShadow::Top | BoxShadow::Right);
p.fillRect(0, st::boxShadow.pxHeight(), width() - st::boxShadow.pxWidth(), height() - st::boxShadow.pxHeight(), st::connectingBG->b);
p.setFont(st::linkFont->f);
p.setPen(st::connectingColor->p);
@@ -557,6 +557,7 @@ void Window::checkAutoLock() {
void Window::setupIntro(bool anim) {
cSetContactsReceived(false);
cSetDialogsReceived(false);
if (intro && (intro->animating() || intro->isVisible()) && !main) return;
QPixmap bg = anim ? myGrab(this, QRect(0, st::titleHeight, width(), height() - st::titleHeight)) : QPixmap();
@@ -606,7 +607,8 @@ void Window::sendServiceHistoryRequest() {
UserData *user = App::userLoaded(ServiceUserId);
if (!user) {
user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_userRequest(MTP_int(ServiceUserId), MTP_string("Telegram"), MTP_string(""), MTP_string(""), MTP_long(-1), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently())));
int32 userFlags = MTPDuser::flag_first_name | MTPDuser::flag_phone | MTPDuser::flag_status;
user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_user(MTP_int(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint())));
}
_serviceHistoryRequest = MTP::send(MTPmessages_GetHistory(user->input, MTP_int(0), MTP_int(0), MTP_int(1)), main->rpcDone(&MainWidget::serviceHistoryDone), main->rpcFail(&MainWidget::serviceHistoryFail));
}
@@ -1415,7 +1417,8 @@ void Window::notifyShowNext(NotifyWindow *remove) {
while (count > 0) {
uint64 next = 0;
HistoryItem *notifyItem = 0;
NotifyWaiters::iterator notifyWaiter;
History *notifyHistory = 0;
NotifyWaiters::iterator notifyWaiter = notifyWaiters.end();
for (NotifyWaiters::iterator i = notifyWaiters.begin(); i != notifyWaiters.end(); ++i) {
History *history = i.key();
if (history->currentNotification() && history->currentNotification()->id != i.value().msg) {
@@ -1423,6 +1426,7 @@ void Window::notifyShowNext(NotifyWindow *remove) {
if (j == notifyWhenMaps.end()) {
history->clearNotifications();
i = notifyWaiters.erase(i);
if (notifyHistory) notifyWaiter = notifyWaiters.find(notifyHistory);
continue;
}
do {
@@ -1438,12 +1442,14 @@ void Window::notifyShowNext(NotifyWindow *remove) {
if (!history->currentNotification()) {
notifyWhenMaps.remove(history);
i = notifyWaiters.erase(i);
if (notifyHistory) notifyWaiter = notifyWaiters.find(notifyHistory);
continue;
}
uint64 when = i.value().when;
if (!notifyItem || next > when) {
next = when;
notifyItem = history->currentNotification();
notifyHistory = history;
notifyWaiter = i;
}
}
@@ -1510,8 +1516,8 @@ void Window::notifyShowNext(NotifyWindow *remove) {
}
if (!history->hasNotification()) {
notifyWaiters.erase(notifyWaiter);
if (j != notifyWhenMaps.end()) notifyWhenMaps.erase(j);
if (notifyWaiter != notifyWaiters.cend()) notifyWaiters.erase(notifyWaiter);
if (j != notifyWhenMaps.cend()) notifyWhenMaps.erase(j);
continue;
}
}

View File

@@ -11,7 +11,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.8.17</string>
<string>0.8.27</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -83,6 +83,7 @@ SOURCES += \
./SourceFiles/app.cpp \
./SourceFiles/application.cpp \
./SourceFiles/audio.cpp \
./SourceFiles/autoupdater.cpp \
./SourceFiles/dialogswidget.cpp \
./SourceFiles/dropdown.cpp \
./SourceFiles/fileuploader.cpp \
@@ -166,6 +167,7 @@ HEADERS += \
./SourceFiles/app.h \
./SourceFiles/application.h \
./SourceFiles/audio.h \
./SourceFiles/autoupdater.h \
./SourceFiles/config.h \
./SourceFiles/countries.h \
./SourceFiles/dialogswidget.h \
@@ -300,7 +302,7 @@ INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/dee-1.0"
INCLUDEPATH += "/usr/include/libdbusmenu-glib-0.4"
LIBS += -lcrypto -lssl -lz -ldl -llzma -lexif -lopus -lopusfile -logg -lopenal
LIBS += -lcrypto -lssl -lz -ldl -llzma -lexif -lopenal -lavformat -lavcodec -lswresample -lavutil -lopus
LIBS += ./../../../Libraries/QtStatic/qtbase/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.a
RESOURCES += \

Binary file not shown.

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