Compare commits

...

37 Commits

Author SHA1 Message Date
John Preston
9e1d5da5a1 version 0.6.14 2014-11-24 16:24:31 +03:00
John Preston
e97cc9f172 fixing http-transport crash 2014-11-24 16:21:27 +03:00
John Preston
3ce8d9f0b7 version 0.6.13 - new icon, critical bug fix in win version 2014-11-24 11:30:43 +03:00
John Preston
b23ffe6c94 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-24 11:23:47 +03:00
John Preston
276ef42c8f fixed crash in win version 2014-11-24 11:23:39 +03:00
John Preston
79a41d541d Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-24 11:23:20 +03:00
John Preston
61e3f9000b icon changed in mac version 2014-11-24 11:23:12 +03:00
John Preston
a4c13e0720 fixed Download button in mediaview 2014-11-24 10:54:06 +03:00
John Preston
a09460dc84 fixed emojis, now work like on iOS 2014-11-24 00:49:14 +03:00
John Preston
4bcfee22ef icon changed in win version 2014-11-23 14:20:40 +03:00
John Preston
880c2697d1 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-22 13:23:40 +03:00
John Preston
188e1b61c5 improved deploy bat 2014-11-22 13:23:30 +03:00
John Preston
696c5df092 improved deploy sh 2014-11-22 13:21:50 +03:00
John Preston
408b38b41f added localstorage to xcode project 2014-11-22 13:01:28 +03:00
John Preston
4a6b6fad77 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-22 12:46:12 +03:00
John Preston
f7fa13899f default recent emojis fixed 2014-11-22 12:45:54 +03:00
John Preston
f370e2b85d version 0.6.12 - local image cache, drafts, shared contact fix, some network fixes 2014-11-22 12:45:04 +03:00
John Preston
5d649f750b fixed maximize button in windows 2014-11-18 18:22:05 +03:00
John Preston
cfcf4d2336 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-18 16:59:38 +03:00
John Preston
922ab40c75 version 0.6.11 fixed photos click area 2014-11-18 16:59:18 +03:00
John Preston
2532245413 fixed xcode warning 2014-11-18 16:18:42 +03:00
John Preston
85ca7e0f05 version 0.6.10 prepared 2014-11-18 15:59:16 +03:00
John Preston
28c8d125cf Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-18 15:41:51 +03:00
John Preston
799a81966a min connection timeout 4secs, fixed photo display, thumbs size is less now, update button text and animation added 2014-11-18 15:41:33 +03:00
John Preston
6333bc59b1 fixed os x dark theme tray icon, added os x main menu 2014-11-18 15:40:43 +03:00
John Preston
d953f894a1 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-15 02:24:00 +03:00
John Preston
868b9843b0 version 0.6.9 - some network and protocol improvements, checkboxes in photos overview, other fixes 2014-11-15 02:23:35 +03:00
John Preston
c89f13bb53 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-13 21:32:24 +03:00
John Preston
722c801f8a fixed pspecific_linux 2014-11-13 10:32:06 -08:00
John Preston
269e588ad0 version 0.6.8 prepared 2014-11-13 21:32:03 +03:00
John Preston
4ee33d3bd9 added os x window icon and qt os x build fix to git 2014-11-13 21:26:39 +03:00
John Preston
85285d9862 fixed os x mouse input, window icon, qt os x build-from-source 2014-11-13 21:26:17 +03:00
John Preston
84226635b2 locations displayed as image links from google maps, killSession crash fixed 2014-11-13 14:27:10 +03:00
John Preston
8ed0cb7bf1 youtube and instagram links wrap fixed for os x 2014-11-13 01:35:00 +03:00
John Preston
55649ad6c4 Merge branch 'master' of https://github.com/telegramdesktop/tdesktop 2014-11-12 23:30:32 +03:00
John Preston
379c5f75e7 os x setup done, fixing localtime on each os x wakeup or application active status change 2014-11-12 23:30:26 +03:00
John Preston
a75f57beb8 youtube and instagram preview display added (instead of messages with only one such link) 2014-11-12 23:18:00 +03:00
157 changed files with 12101 additions and 7333 deletions

View File

@@ -137,7 +137,7 @@ There go to Qt directory
and after that run configure
configure -debug-and-release -opensource -confirm-license -static -opengl desktop -mp -nomake examples -platform win32-msvc2013
configure -debug-and-release -opensource -confirm-license -static -I "D:\TBuild\Libraries\OpenSSL-Win32\include" -L "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Lib" -l Gdi32 -opengl desktop -openssl-linked OPENSSL_LIBS_DEBUG="D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\ssleay32MTd.lib D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\libeay32MTd.lib" OPENSSL_LIBS_RELEASE="D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\ssleay32MT.lib D:\TBuild\Libraries\OpenSSL-Win32\lib\VC\static\libeay32MT.lib" -mp -nomake examples -platform win32-msvc2013
to configure Qt build. After configuration is complete run

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Linux/Release/deploy/$AppVersionStr/tlinuxupd$AppVersion" ]; then
echo "tlinuxupd$AppVersion not found!";

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Linux/Release/deploy/$AppVersionStr/tlinux32upd$AppVersion" ]; then
echo "tlinux32upd$AppVersion not found!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Mac/Release/deploy/$AppVersionStr/tmacupd$AppVersion" ]; then
echo "tmacupd$AppVersion not found!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ ! -f "./../Win32/Deploy/deploy/$AppVersionStr/tupdate$AppVersion" ]; then
echo "tupdate$AppVersion not found!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"

View File

@@ -1,5 +1,5 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
if [ -d "./../Linux/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"

View File

@@ -1,5 +1,9 @@
AppVersionStr=0.6.7
AppVersion=6007
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
echo ""
echo "Preparing version $AppVersionStr.."
echo ""
if [ -d "./../Mac/Release/deploy/$AppVersionStr" ]; then
echo "Deploy folder for version $AppVersionStr already exists!"
@@ -16,11 +20,6 @@ if [ ! -d "./../Mac/Release/Telegram.app" ]; then
exit 1
fi
if [ ! -d "./../Mac/Release/Telegram.app/Contents/_CodeSignature" ]; then
echo "Telegram signature not found!"
exit 1
fi
if [ ! -f "./../Mac/Release/Telegram.app/Contents/Resources/Icon.icns" ]; then
echo "Icon.icns not found in Resources!"
exit 1
@@ -36,18 +35,26 @@ if [ ! -f "./../Mac/Release/Telegram.app/Contents/Frameworks/Updater" ]; then
exit 1
fi
if [ ! -f "./../Mac/Release/Telegram.app.dmg" ]; then
echo "Telegram.app.dmg not found!"
cd ./../Mac/Release && codesign --force --deep --sign "Developer ID Application: John Preston" Telegram.app && cd ./../../Telegram
if [ ! -d "./../Mac/Release/Telegram.app/Contents/_CodeSignature" ]; then
echo "Telegram signature not found!"
exit 1
fi
echo "Preparing version $AppVersionStr, executing Packer.."
cd ./../Mac/Release
temppath=`hdiutil attach -readwrite tsetup.dmg | awk -F "\t" 'END {print $3}'`
cp -R ./Telegram.app "$temppath/"
bless --folder "$temppath/" --openfolder "$temppath/"
hdiutil detach "$temppath"
hdiutil convert tsetup.dmg -format UDZO -imagekey zlib-level=9 -ov -o tsetup.$AppVersionStr.dmg
cd ./../../Telegram
cd ./../Mac/Release && ./Packer.app/Contents/MacOS/Packer -path Telegram.app -version $AppVersion && cd ./../../Telegram
echo "Packer done!"
if [ ! -d "./../Mac/Release/deploy/" ]; then
mkdir "./../Mac/Release/deploy"
fi
echo "Copying Telegram.app and tmacupd$AppVersion to deploy/$AppVersionStr..";
mkdir "./../Mac/Release/deploy/$AppVersionStr"
mkdir "./../Mac/Release/deploy/$AppVersionStr/Telegram"
@@ -55,6 +62,6 @@ cp -r ./../Mac/Release/Telegram.app ./../Mac/Release/deploy/$AppVersionStr/Teleg
rm ./../Mac/Release/Telegram.app/Contents/MacOS/Telegram
rm ./../Mac/Release/Telegram.app/Contents/Frameworks/Updater
mv ./../Mac/Release/tmacupd$AppVersion ./../Mac/Release/deploy/$AppVersionStr/
mv ./../Mac/Release/Telegram.app.dmg ./../Mac/Release/deploy/$AppVersionStr/tsetup.$AppVersionStr.dmg
mv ./../Mac/Release/tsetup.$AppVersionStr.dmg ./../Mac/Release/deploy/$AppVersionStr/tsetup.$AppVersionStr.dmg
echo "Version $AppVersionStr prepared!";

View File

@@ -1,6 +1,49 @@
@echo OFF
set "AppVersionStrSmall=0.6.14"
set "AppVersionStr=0.6.14"
set "AppVersionStrFull=0.6.14.0"
echo.
echo Preparing version %AppVersionStr%..
echo.
set "PATH=%PATH%;C:\Program Files\7-Zip;C:\Program Files (x86)\Inno Setup 5"
cd ..\Win32\Deploy
call ..\..\..\TelegramPrivate\Sign.bat tsetup.0.6.7.exe
call ..\..\..\TelegramPrivate\Sign.bat Telegram.exe
if %errorlevel% neq 0 goto error1
call ..\..\..\TelegramPrivate\Sign.bat Updater.exe
if %errorlevel% neq 0 goto error1
iscc /dMyAppVersion=%AppVersionStrSmall% /dMyAppVersionZero=%AppVersionStr% /dMyAppFullVersion=%AppVersionStrFull% ..\..\Telegram\Setup.iss
if %errorlevel% neq 0 goto error1
call ..\..\..\TelegramPrivate\Sign.bat tsetup.%AppVersionStr%.exe
if %errorlevel% neq 0 goto error1
call Prepare.exe -path Telegram.exe -path Updater.exe
mkdir deploy\0.6.7\Telegram
move deploy\0.6.7\Telegram.exe deploy\0.6.7\Telegram\
if %errorlevel% neq 0 goto error1
cd deploy\%AppVersionStr%
mkdir Telegram
move Telegram.exe Telegram\
7z a -mx9 tportable.%AppVersionStr%.zip Telegram\
if %errorlevel% neq 0 goto error2
echo .
echo Version %AppVersionStr% is ready for deploy!
echo .
cd ..\..\..\..\Telegram
goto eof
:error2
cd ..\..
:error1
cd ..\..\Telegram
echo ERROR occured!
exit /b %errorlevel%
:eof

View File

@@ -22,6 +22,7 @@ lng_maintitle: "Telegram D";
lng_menu_contacts: "Contacts";
lng_menu_settings: "Settings";
lng_menu_about: "About";
lng_menu_update: "Update";
lng_open_from_tray: "Open Telegram";
lng_minimize_to_tray: "Minimize to tray";
@@ -61,7 +62,11 @@ lng_connecting: "Connecting..";
lng_reconnecting: "Reconnect in %1 s..";
lng_reconnecting_try_now: "Try now";
lng_status_offline: "offline";
lng_status_service_notifications: "service notifications";
lng_status_offline: "last seen a long time ago";
lng_status_recently: "last seen recently";
lng_status_last_week: "last seen within a week";
lng_status_last_month: "last seen within a month";
lng_status_invisible: "invisible";
lng_status_lastseen: "last seen {when}";
lng_status_lastseen_now: "just now";
@@ -215,6 +220,15 @@ lng_download_path_clearing: "Clearing..";
lng_download_path_cleared: "Cleared!";
lng_download_path_clear_failed: "Clear failed :(";
lng_settings_section_cache: "Local storage";
lng_settings_no_images_cached: "No cached images found!";
lng_settings_image_cached: "Cached: {count} image, {size}";
lng_settings_images_cached: "Cached: {count} images, {size}";
lng_local_images_clear: "Clear All";
lng_local_images_clearing: "Clearing..";
lng_local_images_cleared: "Cleared!";
lng_local_images_clear_failed: "Clear failed :(";
lng_settings_section_advanced: "Advanced";
lng_connection_type: "Connection type:";
lng_connection_auto_connecting: "Default (connecting..)";
@@ -233,6 +247,7 @@ lng_connection_save: "Save";
lng_settings_reset: "Reset other sessions";
lng_settings_reset_done: "Sessions reset done";
lng_settings_logout: "Log Out";
lng_sure_logout: "Are you sure you want to log out?";
lng_settings_need_restart: "You need to restart for applying
some of the new settings. Restart now?";
@@ -473,5 +488,21 @@ lng_mac_always_open_with: "Always Open With";
lng_mac_this_app_can_open: "This application can open \"{file}\".";
lng_mac_not_known_app: "It's not known if this application can open \"{file}\".";
// Keys finished
lng_mac_menu_about: "About Telegram";
lng_mac_menu_preferences: "Preferences...";
lng_mac_menu_file: "File";
lng_mac_menu_logout: "Log Out";
lng_mac_menu_edit: "Edit";
lng_mac_menu_undo: "Undo";
lng_mac_menu_redo: "Redo";
lng_mac_menu_cut: "Cut";
lng_mac_menu_copy: "Copy";
lng_mac_menu_paste: "Paste";
lng_mac_menu_delete: "Delete";
lng_mac_menu_select_all: "Select All";
lng_mac_menu_window: "Window";
lng_mac_menu_contacts: "Contacts";
lng_mac_menu_new_group: "New Group";
lng_mac_menu_show: "Show Telegram";
// Keys finished

View File

@@ -29,7 +29,7 @@ emojiPadding: 0px;
counterBG: #f23c34;
counterMuteBG: #888;
counterColor: #fff;
counterMacInvColor: #045fd5;
counterMacInvColor: #ffffff01;
lineWidth: 1px;
@@ -86,6 +86,7 @@ sysUpd: sysButton {
overColor: white;
duration: 150;
}
updateBlinkDuration: 500;
sysMin: sysButton(sysUpd) {
img: sprite(207px, 1px, 19px, 19px);
}
@@ -1572,6 +1573,9 @@ medviewPhotoSpritePos: point(14px, 14px);
overviewPhotoSkip: 10px;
overviewPhotoMinSize: 100px;
overviewPhotoCheck: sprite(245px, 308px, 32px, 32px);
overviewPhotoChecked: sprite(278px, 308px, 32px, 32px);
overviewPhotoSelectOverlay: #0a7bb03f;
// Mac specific
@@ -1621,6 +1625,7 @@ mediaviewLoaderSkip: 9px;
minPhotoWidth: 90px;
minPhotoHeight: 90px;
maxMediaSize: 420px;
usernameFont: font(14px);
usernameColor: #777;
@@ -1635,3 +1640,7 @@ usernameDone: flatButton(btnSelectDone) {
usernameCancel: flatButton(btnSelectCancel) {
width: 167px;
}
youtubeIcon: sprite(336px, 221px, 60px, 60px);
vimeoIcon: sprite(336px, 283px, 60px, 60px);
locationSize: size(320, 240);

View File

@@ -3,9 +3,6 @@
#define MyAppShortName "Telegram"
#define MyAppName "Telegram Desktop"
#define MyAppVersion "0.6.7"
#define MyAppVersionZero "0.6.7"
#define MyAppFullVersion "0.6.7.0"
#define MyAppPublisher "Telegram Messenger LLP"
#define MyAppURL "https://tdesktop.com"
#define MyAppExeName "Telegram.exe"
@@ -28,7 +25,7 @@ DefaultGroupName={#MyAppName}
AllowNoIcons=yes
OutputDir=.\..\Win32\Deploy
OutputBaseFilename=tsetup.{#MyAppVersionZero}
SetupIconFile=.\SourceFiles\art\iconround256.ico
SetupIconFile=.\SourceFiles\art\icon256.ico
UninstallDisplayIcon={app}\Telegram.exe
Compression=lzma
SolidCompression=yes

View File

@@ -1 +0,0 @@
codesign --force --deep --sign "Developer ID Application: John Preston" ./../Mac/Release/Telegram.app

View File

@@ -1,4 +0,0 @@
cd ..\Win32\Deploy
call ..\..\..\TelegramPrivate\Sign.bat Telegram.exe
call ..\..\..\TelegramPrivate\Sign.bat Updater.exe
cd ..\..\Telegram

View File

@@ -78,11 +78,11 @@ EmojiReplace replaces[] = {
{0xD83DDE37U, ":X"},
{0xD83DDE1AU, ":-*"},
{0xD83DDE08U, "}:)"},
{0x2764FE0FU, "<3"},
{0x2764U, "<3"},
{0xD83DDC4DU, ":like:"},
{0xD83DDC4EU, ":dislike:"},
{0x261DFE0FU, ":up:"},
{0x270CFE0FU, ":v:"},
{0x261DU, ":up:"},
{0x270CU, ":v:"},
{0xD83DDC4CU, ":ok:"}
};
const uint32 replacesCount = sizeof(replaces) / sizeof(EmojiReplace);
@@ -102,7 +102,7 @@ uint64 emojiCategory0[] = {
0xD83DDE03LLU,
0xD83DDE00LLU,
0xD83DDE0ALLU,
0x263AFE0FLLU,
0x263ALLU,
0xD83DDE09LLU,
0xD83DDE0DLLU,
0xD83DDE18LLU,
@@ -208,7 +208,7 @@ uint64 emojiCategory0[] = {
0xD83DDC4CLLU,
0xD83DDC4ALLU,
0x270ALLU,
0x270CFE0FLLU,
0x270CLLU,
0xD83DDC4BLLU,
0x270BLLU,
0xD83DDC50LLU,
@@ -218,7 +218,7 @@ uint64 emojiCategory0[] = {
0xD83DDC48LLU,
0xD83DDE4CLLU,
0xD83DDE4FLLU,
0x261DFE0FLLU,
0x261DLLU,
0xD83DDC4FLLU,
0xD83DDCAALLU,
0xD83DDEB6LLU,
@@ -270,7 +270,7 @@ uint64 emojiCategory0[] = {
0xD83DDC99LLU,
0xD83DDC9CLLU,
0xD83DDC9ALLU,
0x2764FE0FLLU,
0x2764LLU,
0xD83DDC94LLU,
0xD83DDC97LLU,
0xD83DDC93LLU,
@@ -395,12 +395,12 @@ uint64 emojiCategory1[] = {
0xD83CDF0CLLU,
0xD83CDF20LLU,
0x2B50LLU,
0x2600FE0FLLU,
0x2600LLU,
0x26C5LLU,
0x2601FE0FLLU,
0x2601LLU,
0x26A1LLU,
0x2614LLU,
0x2744FE0FLLU,
0x2744LLU,
0x26C4LLU,
0xD83CDF00LLU,
0xD83CDF01LLU,
@@ -440,7 +440,7 @@ uint64 emojiCategory2[] = {
0xD83DDCBELLU,
0xD83DDCBBLLU,
0xD83DDCF1LLU,
0x260EFE0FLLU,
0x260ELLU,
0xD83DDCDELLU,
0xD83DDCDFLLU,
0xD83DDCE0LLU,
@@ -497,7 +497,7 @@ uint64 emojiCategory2[] = {
0xD83DDCE7LLU,
0xD83DDCE5LLU,
0xD83DDCE4LLU,
0x2709FE0FLLU,
0x2709LLU,
0xD83DDCE9LLU,
0xD83DDCE8LLU,
0xD83DDCEFLLU,
@@ -521,11 +521,11 @@ uint64 emojiCategory2[] = {
0xD83DDCC7LLU,
0xD83DDCC1LLU,
0xD83DDCC2LLU,
0x2702FE0FLLU,
0x2702LLU,
0xD83DDCCCLLU,
0xD83DDCCELLU,
0x2712FE0FLLU,
0x270FFE0FLLU,
0x2712LLU,
0x270FLLU,
0xD83DDCCFLLU,
0xD83DDCD0LLU,
0xD83DDCD5LLU,
@@ -564,7 +564,7 @@ uint64 emojiCategory2[] = {
0xD83CDFC8LLU,
0xD83CDFC0LLU,
0x26BDLLU,
0x26BEFE0FLLU,
0x26BELLU,
0xD83CDFBELLU,
0xD83CDFB1LLU,
0xD83CDFC9LLU,
@@ -680,7 +680,7 @@ uint64 emojiCategory3[] = {
0xD83DDEA3LLU,
0x2693LLU,
0xD83DDE80LLU,
0x2708FE0FLLU,
0x2708LLU,
0xD83DDCBALLU,
0xD83DDE81LLU,
0xD83DDE82LLU,
@@ -721,13 +721,13 @@ uint64 emojiCategory3[] = {
0xD83CDFABLLU,
0xD83DDEA6LLU,
0xD83DDEA5LLU,
0x26A0FE0FLLU,
0x26A0LLU,
0xD83DDEA7LLU,
0xD83DDD30LLU,
0x26FDLLU,
0xD83CDFEELLU,
0xD83CDFB0LLU,
0x2668FE0FLLU,
0x2668LLU,
0xD83DDDFFLLU,
0xD83CDFAALLU,
0xD83CDFADLLU,
@@ -760,33 +760,33 @@ uint64 emojiCategory4[] = {
0xD83DDD22LLU,
0x2320E3LLU,
0xD83DDD23LLU,
0x2B06FE0FLLU,
0x2B07FE0FLLU,
0x2B05FE0FLLU,
0x27A1FE0FLLU,
0x2B06LLU,
0x2B07LLU,
0x2B05LLU,
0x27A1LLU,
0xD83DDD20LLU,
0xD83DDD21LLU,
0xD83DDD24LLU,
0x2197FE0FLLU,
0x2196FE0FLLU,
0x2198FE0FLLU,
0x2199FE0FLLU,
0x2194FE0FLLU,
0x2195FE0FLLU,
0x2197LLU,
0x2196LLU,
0x2198LLU,
0x2199LLU,
0x2194LLU,
0x2195LLU,
0xD83DDD04LLU,
0x25C0FE0FLLU,
0x25B6FE0FLLU,
0x25C0LLU,
0x25B6LLU,
0xD83DDD3CLLU,
0xD83DDD3DLLU,
0x21A9FE0FLLU,
0x21AAFE0FLLU,
0x2139FE0FLLU,
0x21A9LLU,
0x21AALLU,
0x2139LLU,
0x23EALLU,
0x23E9LLU,
0x23EBLLU,
0x23ECLLU,
0x2935FE0FLLU,
0x2934FE0FLLU,
0x2935LLU,
0x2934LLU,
0xD83CDD97LLU,
0xD83DDD00LLU,
0xD83DDD01LLU,
@@ -802,8 +802,8 @@ uint64 emojiCategory4[] = {
0xD83CDE2FLLU,
0xD83CDE33LLU,
0xD83CDE35LLU,
0xD83CDE32LLU,
0xD83CDE34LLU,
0xD83CDE32LLU,
0xD83CDE50LLU,
0xD83CDE39LLU,
0xD83CDE3ALLU,
@@ -822,14 +822,14 @@ uint64 emojiCategory4[] = {
0xD83CDE37LLU,
0xD83CDE38LLU,
0xD83CDE02LLU,
0x24C2FE0FLLU,
0x24C2LLU,
0xD83DDEC2LLU,
0xD83DDEC4LLU,
0xD83DDEC5LLU,
0xD83DDEC3LLU,
0xD83CDE51LLU,
0x3299FE0FLLU,
0x3297FE0FLLU,
0x3299LLU,
0x3297LLU,
0xD83CDD91LLU,
0xD83CDD98LLU,
0xD83CDD94LLU,
@@ -842,11 +842,11 @@ uint64 emojiCategory4[] = {
0xD83DDEB7LLU,
0xD83DDEB8LLU,
0x26D4LLU,
0x2733FE0FLLU,
0x2747FE0FLLU,
0x2733LLU,
0x2747LLU,
0x274ELLU,
0x2705LLU,
0x2734FE0FLLU,
0x2734LLU,
0xD83DDC9FLLU,
0xD83CDD9ALLU,
0xD83DDCF3LLU,
@@ -857,7 +857,7 @@ uint64 emojiCategory4[] = {
0xD83CDD7ELLU,
0xD83DDCA0LLU,
0x27BFLLU,
0x267BFE0FLLU,
0x267BLLU,
0x2648LLU,
0x2649LLU,
0x264ALLU,
@@ -880,8 +880,8 @@ uint64 emojiCategory4[] = {
0xAELLU,
0x2122LLU,
0x274CLLU,
0x203CFE0FLLU,
0x2049FE0FLLU,
0x203CLLU,
0x2049LLU,
0x2757LLU,
0x2753LLU,
0x2755LLU,
@@ -921,26 +921,26 @@ uint64 emojiCategory4[] = {
0x2795LLU,
0x2796LLU,
0x2797LLU,
0x2660FE0FLLU,
0x2665FE0FLLU,
0x2663FE0FLLU,
0x2666FE0FLLU,
0x2660LLU,
0x2665LLU,
0x2663LLU,
0x2666LLU,
0xD83DDCAELLU,
0xD83DDCAFLLU,
0x2714FE0FLLU,
0x2611FE0FLLU,
0x2714LLU,
0x2611LLU,
0xD83DDD18LLU,
0xD83DDD17LLU,
0x27B0LLU,
0x3030LLU,
0x303DFE0FLLU,
0x303DLLU,
0xD83DDD31LLU,
0x25FCFE0FLLU,
0x25FBFE0FLLU,
0x25FCLLU,
0x25FBLLU,
0x25FELLU,
0x25FDLLU,
0x25AAFE0FLLU,
0x25ABFE0FLLU,
0x25AALLU,
0x25ABLLU,
0xD83DDD3ALLU,
0xD83DDD32LLU,
0xD83DDD33LLU,
@@ -957,61 +957,105 @@ uint64 emojiCategory4[] = {
0xD83DDD39LLU,
};
uint64 emojiClones[] = {
0x263ALLU,
0x270CLLU,
0x261DLLU,
0x2764LLU,
0x2600LLU,
0x2601LLU,
0x2744LLU,
0x260ELLU,
0x2709LLU,
0x2702LLU,
0x2712LLU,
0x270FLLU,
0x26BELLU,
0x2708LLU,
0x26A0LLU,
0x2668LLU,
0x2B06LLU,
0x2B07LLU,
0x2B05LLU,
0x27A1LLU,
0x2197LLU,
0x2196LLU,
0x2198LLU,
0x2199LLU,
0x2194LLU,
0x2195LLU,
0x25C0LLU,
0x25B6LLU,
0x21A9LLU,
0x21AALLU,
0x2139LLU,
0x2935LLU,
0x2934LLU,
0x24C2LLU,
0x3299LLU,
0x3297LLU,
0x2733LLU,
0x2747LLU,
0x2734LLU,
0x267BLLU,
uint64 emojiPostfixed[] = {
0x203CLLU,
0x2049LLU,
0x2660LLU,
0x2665LLU,
0x2663LLU,
0x2666LLU,
0x2714LLU,
0x2611LLU,
0x303DLLU,
0x25FCLLU,
0x25FBLLU,
0x2139LLU,
0x2194LLU,
0x2195LLU,
0x2196LLU,
0x2197LLU,
0x2198LLU,
0x2199LLU,
0x21A9LLU,
0x21AALLU,
0x231ALLU,
0x231BLLU,
0x24C2LLU,
0x25AALLU,
0x25ABLLU,
0x25B6LLU,
0x25C0LLU,
0x25FBLLU,
0x25FCLLU,
0x25FDLLU,
0x25FELLU,
0x2600LLU,
0x2601LLU,
0x260ELLU,
0x2611LLU,
0x2614LLU,
0x2615LLU,
0x261DLLU,
0x263ALLU,
0x2648LLU,
0x2649LLU,
0x264ALLU,
0x264BLLU,
0x264CLLU,
0x264DLLU,
0x264ELLU,
0x264FLLU,
0x2650LLU,
0x2651LLU,
0x2652LLU,
0x2653LLU,
0x2660LLU,
0x2663LLU,
0x2665LLU,
0x2666LLU,
0x2668LLU,
0x267BLLU,
0x267FLLU,
0x2693LLU,
0x26A0LLU,
0x26A1LLU,
0x26AALLU,
0x26ABLLU,
0x26BDLLU,
0x26BELLU,
0x26C4LLU,
0x26C5LLU,
0x26D4LLU,
0x26EALLU,
0x26F2LLU,
0x26F3LLU,
0x26F5LLU,
0x26FALLU,
0x26FDLLU,
0x2702LLU,
0x2708LLU,
0x2709LLU,
0x270CLLU,
0x270FLLU,
0x2712LLU,
0x2714LLU,
0x2716LLU,
0x2733LLU,
0x2734LLU,
0x2744LLU,
0x2747LLU,
0x2757LLU,
0x2764LLU,
0x27A1LLU,
0x2934LLU,
0x2935LLU,
0x2B05LLU,
0x2B06LLU,
0x2B07LLU,
0x2B50LLU,
0x2B55LLU,
0x2B1BLLU,
0x2B1CLLU,
0x303DLLU,
0x3297LLU,
0x3299LLU,
0xD83CDC04LLU,
0xD83CDD7FLLU,
0xD83CDE1ALLU,
0xD83CDE2FLLU,
};
QMap<uint64, bool> emojiWithPostfixes;
uint32 firstCode(uint64 fullCode) {
return (fullCode > 0xFFFFFFFFLLU) ? uint32(fullCode >> 32) : (fullCode & 0xFFFFFFFFU);
@@ -1046,7 +1090,7 @@ void writeEmojiCategory(QTextStream &tcpp, uint64 *emojiCategory, uint32 size, c
bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_png) {
int currentRow = 0, currentColumn = 0;
uint32 min1 = 0xFFFFFFFFU, max1 = 0, min2 = 0xFFFFFFFFU, max2 = 0, min3 = 0xFFFFFFFFU, max3 = 0;
uint32 min1 = 0xFFFFFFFFU, max1 = 0, min2 = 0xFFFFFFFFU, max2 = 0;
QImage sprites[5];
int emojisInRow[] = { 27, 29, 33, 34, 34 }; // [[7,27],[4,29],[7,33],[3,34],[6,34]]
@@ -1100,13 +1144,8 @@ bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_p
}
} else if (high == 35 || (high >= 48 && high < 58)) { // digits
} else {
if (high < 0xD000) {
if (data.code < min2) min2 = data.code;
if (data.code > max2) max2 = data.code;
} else {
if (data.code < min3) min3 = data.code;
if (data.code > max3) max3 = data.code;
}
if (data.code < min2) min2 = data.code;
if (data.code > max2) max2 = data.code;
}
EmojisData::const_iterator k = emojisData.constFind(data.code);
if (k != emojisData.cend()) {
@@ -1170,23 +1209,8 @@ bool genEmoji(QString emoji_in, const QString &emoji_out, const QString &emoji_p
}
}
for (int i = 0, l = sizeof(emojiClones) / sizeof(emojiClones[0]); i < l; ++i) {
uint64 cloneCode = emojiClones[i], originalCode = (cloneCode << 16) | 0xFE0F;
EmojisData::const_iterator j = emojisData.constFind(originalCode);
if (j == emojisData.cend()) {
cout << "Could not find data for emoji clone 0x" << QString::number(cloneCode, 16).toUpper().toUtf8().constData() << "!\n";
return false;
}
EmojiData clone;
clone.code = firstCode(cloneCode);
clone.code2 = secondCode(cloneCode);
clone.category = -1;
clone.index = -1;
clone.x = j->x;
clone.y = j->y;
emojisData.insert(cloneCode, clone);
if (clone.code < min1) min1 = clone.code;
if (clone.code > max1) max1 = clone.code;
for (int i = 0, l = sizeof(emojiPostfixed) / sizeof(emojiPostfixed[0]); i < l; ++i) {
emojiWithPostfixes.insert(emojiPostfixed[i], true);
}
try {
@@ -1229,7 +1253,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com\n\
tcpp << "\tEmojiData *toFill = emojis = (EmojiData*)emojisData;\n\n";
for (EmojisData::const_iterator i = emojisData.cbegin(), e = emojisData.cend(); i != e; ++i) {
int len = i->code2 ? 4 : ((i->code >> 16) ? 2 : 1);
tcpp << "\tnew (toFill++) EmojiData(" << (i->x * imSize) << ", " << (i->y * imSize) << ", 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U, 0x" << QString("%1").arg(i->code2, 0, 16).toUpper().toUtf8().constData() << "U, " << len << ");\n";
bool withPostfix = emojiWithPostfixes.constFind(i->code) != emojiWithPostfixes.constEnd();
tcpp << "\tnew (toFill++) EmojiData(" << (i->x * imSize) << ", " << (i->y * imSize) << ", 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U, 0x" << QString("%1").arg(i->code2, 0, 16).toUpper().toUtf8().constData() << "U, " << len << (withPostfix ? ", 0xFE0F" : "") << ");\n";
}
tcpp << "}\n\n";
}
@@ -1283,18 +1308,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com\n\
tcpp << "\t\treturn 0;\n";
tcpp << "\t}\n\n";
tcpp << "\tif (code >= 0x" << QString("%1").arg(min2, 0, 16).toUpper().toUtf8().constData() << "U && code <= 0x" << QString("%1").arg(max2, 0, 16).toUpper().toUtf8().constData() << "U) {\n";
tcpp << "\t\tif ((code & 0xFFFFU) != 0xFE0F) return 0;\n\n";
tcpp << "\t\tswitch (code) {\n";
for (; i != e; ++i) {
if (i->code2 || ((i->code >> 16) >= 0xD000)) break;
tcpp << "\t\t\tcase 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U: return &emojis[" << (index++) << "];\n";
}
tcpp << "\t\t}\n\n";
tcpp << "\t\treturn 0;\n";
tcpp << "\t}\n\n";
tcpp << "\tif (code < 0x" << QString("%1").arg(min3, 0, 16).toUpper().toUtf8().constData() << "U || code > 0x" << QString("%1").arg(max3, 0, 16).toUpper().toUtf8().constData() << "U) return 0;\n\n";
tcpp << "\tif (code < 0x" << QString("%1").arg(min2, 0, 16).toUpper().toUtf8().constData() << "U || code > 0x" << QString("%1").arg(max2, 0, 16).toUpper().toUtf8().constData() << "U) return 0;\n\n";
tcpp << "\tswitch (code) {\n";
for (; i != e; ++i) {
tcpp << "\tcase 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U: return &emojis[" << (index++) << "];\n";

View File

@@ -94,6 +94,5 @@ int main(int argc, char *argv[])
}
}
int res = prepare(f, paths);
system("PAUSE");
return res;
}

View File

@@ -26,6 +26,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "mainwidget.h"
#include <libexif/exif-data.h>
#include "localstorage.h"
namespace {
bool quiting = false;
@@ -43,6 +45,9 @@ namespace {
typedef QHash<AudioId, AudioData*> AudiosData;
AudiosData audiosData;
typedef QHash<QString, ImageLinkData*> ImageLinksData;
ImageLinksData imageLinksData;
typedef QHash<DocumentId, DocumentData*> DocumentsData;
DocumentsData documentsData;
@@ -111,7 +116,7 @@ namespace App {
bool loggedOut() {
Window *w(wnd());
if (w) {
w->tempDirDelete();
w->tempDirDelete(Local::ClearManagerAll);
w->notifyClearFast();
w->setupIntro(true);
}
@@ -162,6 +167,31 @@ namespace App {
return (peer_id & 0x100000000L) ? int32(peer_id & 0xFFFFFFFFL) : 0;
}
int32 onlineForSort(int32 online, int32 now) {
if (online <= 0) {
switch (online) {
case -2: {
QDate yesterday(date(now).date());
yesterday.addDays(-3);
return int32(QDateTime(yesterday).toTime_t());
} break;
case -3: {
QDate weekago(date(now).date());
weekago.addDays(-7);
return int32(QDateTime(weekago).toTime_t());
} break;
case -4: {
QDate monthago(date(now).date());
monthago.addDays(-30);
return int32(QDateTime(monthago).toTime_t());
} break;
}
}
return online;
}
int32 onlineWillChangeIn(int32 online, int32 now) {
if (online <= 0) return 86400;
if (online > now) {
@@ -179,9 +209,20 @@ namespace App {
return dNow.secsTo(dTomorrow);
}
QString onlineText(int32 online, int32 now, bool precise) {
if (!online) return lang(lng_status_offline);
if (online < 0) return lang(lng_status_invisible);
QString onlineText(UserData *user, int32 now, bool precise) {
if (isServiceUser(user->id)) {
return lang(lng_status_service_notifications);
}
int32 online = user->onlineTill;
if (online <= 0) {
switch (online) {
case 0: return lang(lng_status_offline);
case -2: return lang(lng_status_recently);
case -3: return lang(lng_status_last_week);
case -4: return lang(lng_status_last_month);
}
return lang(lng_status_invisible);
}
if (online > now) {
return lang(lng_status_online);
}
@@ -274,7 +315,10 @@ namespace App {
data->contact = -1;
status = &d.vstatus;
::self = data;
if (::self != data) {
::self = data;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
} break;
case mtpc_userContact: {
const MTPDuserContact &d(user.c_userContact());
@@ -299,7 +343,7 @@ namespace App {
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)), (data->id != 333000 && !data->phone.isEmpty()) ? formatPhone(data->phone) : QString(), textOneLine(qs(d.vusername)));
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);
@@ -326,6 +370,10 @@ namespace App {
data->loaded = true;
if (status) switch (status->type()) {
case mtpc_userStatusEmpty: data->onlineTill = 0; break;
case mtpc_userStatusRecently: data->onlineTill = -2; break;
case mtpc_userStatusLastWeek: data->onlineTill = -3; break;
case mtpc_userStatusLastMonth: data->onlineTill = -4; break;
case mtpc_userStatusOffline: data->onlineTill = status->c_userStatusOffline().vwas_online.v; break;
case mtpc_userStatusOnline: data->onlineTill = status->c_userStatusOnline().vexpires.v; break;
}
@@ -405,7 +453,7 @@ namespace App {
if (!data) continue;
data->loaded = true;
data->updateName(title.trimmed(), QString());
data->updateName(title.trimmed(), QString(), QString());
if (App::main()) App::main()->peerUpdated(data);
}
@@ -561,13 +609,20 @@ namespace App {
}
void feedWereDeleted(const QVector<MTPint> &msgsIds) {
bool resized = false;
for (QVector<MTPint>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
MsgsData::const_iterator j = msgsData.constFind(i->v);
if (j != msgsData.cend()) {
History *h = (*j)->history();
(*j)->destroy();
if (App::main() && h->peer == App::main()->peer()) {
resized = true;
}
}
}
if (resized) {
App::main()->itemResized(0);
}
}
void feedUserLinks(const MTPVector<MTPcontacts_Link> &links) {
@@ -634,7 +689,7 @@ namespace App {
App::main()->removeContact(user);
}
}
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), (user->contact || user->id == 333000 || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), textOneLine(user->username));
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), (user->contact || isServiceUser(user->id) || user->phone.isEmpty()) ? QString() : App::formatPhone(user->phone), textOneLine(user->username));
if (App::main()) App::main()->peerUpdated(user);
}
}
@@ -911,14 +966,6 @@ namespace App {
return result;
}
void forgetPhotos() {
lastPhotos.clear();
lastPhotosMap.clear();
for (PhotosData::const_iterator i = photosData.cbegin(), e = photosData.cend(); i != e; ++i) {
i.value()->forget();
}
}
VideoData *video(const VideoId &video, VideoData *convert, const uint64 &access, int32 user, int32 date, int32 duration, int32 w, int32 h, const ImagePtr &thumb, int32 dc, int32 size) {
if (convert) {
if (convert->id != video) {
@@ -967,12 +1014,6 @@ namespace App {
return result;
}
void forgetVideos() {
for (VideosData::const_iterator i = videosData.cbegin(), e = videosData.cend(); i != e; ++i) {
i.value()->forget();
}
}
AudioData *audio(const AudioId &audio, AudioData *convert, const uint64 &access, int32 user, int32 date, int32 duration, int32 dc, int32 size) {
if (convert) {
if (convert->id != audio) {
@@ -1015,12 +1056,6 @@ namespace App {
return result;
}
void forgetAudios() {
for (AudiosData::const_iterator i = audiosData.cbegin(), e = audiosData.cend(); i != e; ++i) {
i.value()->forget();
}
}
DocumentData *document(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 user, int32 date, const QString &name, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size) {
if (convert) {
if (convert->id != document) {
@@ -1067,10 +1102,37 @@ namespace App {
return result;
}
void forgetDocuments() {
ImageLinkData *imageLink(const QString &imageLink, ImageLinkType type, const QString &url) {
ImageLinksData::const_iterator i = imageLinksData.constFind(imageLink);
ImageLinkData *result;
if (i == imageLinksData.cend()) {
result = new ImageLinkData(imageLink);
imageLinksData.insert(imageLink, result);
result->type = type;
} else {
result = i.value();
}
return result;
}
void forgetMedia() {
lastPhotos.clear();
lastPhotosMap.clear();
for (PhotosData::const_iterator i = photosData.cbegin(), e = photosData.cend(); i != e; ++i) {
i.value()->forget();
}
for (VideosData::const_iterator i = videosData.cbegin(), e = videosData.cend(); i != e; ++i) {
i.value()->forget();
}
for (AudiosData::const_iterator i = audiosData.cbegin(), e = audiosData.cend(); i != e; ++i) {
i.value()->forget();
}
for (DocumentsData::const_iterator i = documentsData.cbegin(), e = documentsData.cend(); i != e; ++i) {
i.value()->forget();
}
for (ImageLinksData::const_iterator i = imageLinksData.cbegin(), e = imageLinksData.cend(); i != e; ++i) {
i.value()->thumb->forget();
}
}
MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo) {
@@ -1224,6 +1286,7 @@ namespace App {
lastPhotos.clear();
lastPhotosMap.clear();
::self = 0;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
/* // don't delete history without deleting its' peerdata
void deleteHistory(const PeerId &peer) {
@@ -1597,7 +1660,7 @@ namespace App {
}
QByteArray encrypted(16 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &MTP::localKey(), encrypted.constData());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &Local::oldKey(), encrypted.constData());
DEBUG_LOG(("App Info: writing user config file"));
QDataStream configStream(&configFile);
@@ -1648,7 +1711,7 @@ namespace App {
}
cSetLocalSalt(salt);
MTP::createLocalKey(QByteArray(), &salt);
Local::createOldKey(&salt);
if (data.size() <= 16 || (data.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size: %1").arg(data.size()));
@@ -1657,7 +1720,7 @@ namespace App {
uint32 fullDataLen = data.size() - 16;
decrypted.resize(fullDataLen);
const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &MTP::localKey(), dataKey);
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &Local::oldKey(), dataKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
LOG(("App Error: bad decrypt key, data from user-config not decrypted"));
@@ -1854,10 +1917,7 @@ namespace App {
void checkImageCacheSize() {
int64 nowImageCacheSize = imageCacheSize();
if (nowImageCacheSize > serviceImageCacheSize + MemoryForImageCache) {
App::forgetPhotos();
App::forgetVideos();
App::forgetAudios();
App::forgetDocuments();
App::forgetMedia();
serviceImageCacheSize = imageCacheSize();
}
}
@@ -1887,7 +1947,6 @@ namespace App {
::quiting = true;
}
QImage readImage(QByteArray data, QByteArray *format) {
QByteArray tmpFormat;
QImage result;

View File

@@ -63,8 +63,9 @@ 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);
QString onlineText(int32 onlineOnServer, int32 nowOnServer, bool precise = false);
QString onlineText(UserData *user, int32 nowOnServer, bool precise = false);
void feedUsers(const MTPVector<MTPUser> &users);
void feedChats(const MTPVector<MTPChat> &chats);
@@ -102,13 +103,11 @@ namespace App {
ChatData *chat(int32 chat);
QString peerName(const PeerData *peer, bool forDialogs = false);
PhotoData *photo(const PhotoId &photo, PhotoData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr());
void forgetPhotos();
VideoData *video(const VideoId &video, VideoData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 w = 0, int32 h = 0, const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0);
void forgetVideos();
AudioData *audio(const AudioId &audio, AudioData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, int32 duration = 0, int32 dc = 0, int32 size = 0);
void forgetAudios();
DocumentData *document(const DocumentId &document, DocumentData *convert = 0, const uint64 &access = 0, int32 user = 0, int32 date = 0, const QString &name = QString(), const QString &mime = QString(), const ImagePtr &thumb = ImagePtr(), int32 dc = 0, int32 size = 0);
void forgetDocuments();
ImageLinkData *imageLink(const QString &imageLink, ImageLinkType type = InvalidImageLink, const QString &url = QString());
void forgetMedia();
MTPPhoto photoFromUserPhoto(MTPint userId, MTPint date, const MTPUserProfilePhoto &photo);

View File

@@ -28,6 +28,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "boxes/confirmbox.h"
#include "langloaderplain.h"
#include "localstorage.h"
namespace {
Application *mainApp = 0;
FileUploader *uploader = 0;
@@ -131,6 +133,7 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
}
}
Local::start();
style::startManager();
anim::startManager();
historyInit();
@@ -139,8 +142,6 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
psInstallEventFilter();
updateCheckTimer.setSingleShot(true);
connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected()));
connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
connect(&socket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(socketError(QLocalSocket::LocalSocketError)));
@@ -151,10 +152,10 @@ Application::Application(int &argc, char **argv) : PsApplication(argc, argv),
connect(&updateCheckTimer, SIGNAL(timeout()), this, SLOT(startUpdateCheck()));
connect(this, SIGNAL(updateFailed()), this, SLOT(onUpdateFailed()));
connect(this, SIGNAL(updateReady()), this, SLOT(onUpdateReady()));
connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState)));
connect(&writeUserConfigTimer, SIGNAL(timeout()), this, SLOT(onWriteUserConfig()));
writeUserConfigTimer.setSingleShot(true);
killDownloadSessionsTimer.setSingleShot(true);
connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions()));
if (cManyInstance()) {
@@ -345,10 +346,18 @@ void Application::killDownloadSessionsStop(int32 dc) {
}
}
void Application::checkLocalTime() {
if (App::main()) App::main()->checkLastUpdate(checkms());
}
void Application::onWriteUserConfig() {
App::writeUserConfig();
}
void Application::onAppStateChanged(Qt::ApplicationState state) {
checkLocalTime();
}
void Application::killDownloadSessions() {
uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
for (QMap<int32, uint64>::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {
@@ -611,9 +620,11 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
}
void Application::startApp() {
Local::ReadMapState state = Local::readMap(QByteArray());
App::readUserConfig();
if (!MTP::localKey().created()) {
MTP::createLocalKey(QByteArray());
if (!Local::oldKey().created()) {
Local::createOldKey();
cSetNeedConfigResave(true);
}
if (cNeedConfigResave()) {
@@ -627,12 +638,12 @@ void Application::startApp() {
readSupportTemplates();
MTP::setLayer(mtpLayerMax);
MTP::start();
MTP::setStateChangedHandler(mtpStateChanged);
MTP::setSessionResetHandler(mtpSessionReset);
initImageLinkManager();
App::initMedia();
if (MTP::authedId()) {
@@ -746,6 +757,7 @@ Application::~Application() {
socket.close();
closeApplication();
App::deinitMedia();
deinitImageLinkManager();
mainApp = 0;
delete updateReply;
delete ::uploader;
@@ -758,6 +770,7 @@ Application::~Application() {
delete window;
style::stopManager();
Local::stop();
}
Application *Application::app() {

View File

@@ -73,11 +73,15 @@ public:
void killDownloadSessionsStart(int32 dc);
void killDownloadSessionsStop(int32 dc);
void checkLocalTime();
signals:
void peerPhotoDone(PeerId peer);
void peerPhotoFail(PeerId peer);
void adjustSingleTimers();
public slots:
void startUpdateCheck(bool forceWait = false);
@@ -104,13 +108,14 @@ public slots:
void onWriteUserConfig();
void killDownloadSessions();
void onAppStateChanged(Qt::ApplicationState state);
private:
QMap<MsgId, PeerId> photoUpdates;
QMap<int32, uint64> killDownloadSessionTimes;
QTimer killDownloadSessionsTimer;
SingleTimer killDownloadSessionsTimer;
void startApp();
@@ -131,10 +136,10 @@ private:
mtpRequestId updateRequestId;
QNetworkAccessManager updateManager;
QNetworkReply *updateReply;
QTimer updateCheckTimer;
SingleTimer updateCheckTimer;
QThread *updateThread;
PsUpdateDownloader *updateDownloader;
QTimer writeUserConfigTimer;
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -348,7 +348,6 @@ void VoiceMessagesFader::onTimer() {
VoiceMessages *voice = audioVoice();
if (!voice) return;
uint64 ms = getms();
for (int32 i = 0; i < AudioVoiceMsgSimultaneously; ++i) {
VoiceMessages::Msg &m(voice->_data[i]);
if (m.state == VoiceMessageStopped || m.state == VoiceMessagePaused || !m.source) continue;

View File

@@ -281,7 +281,7 @@ bool AddContactBox::onSaveFail(const RPCError &error) {
QString err(error.type());
QString firstName = _firstInput.text().trimmed(), lastName = _lastInput.text().trimmed();
if (err == "CHAT_TITLE_NOT_MODIFIED") {
_peer->updateName(firstName, QString());
_peer->updateName(firstName, QString(), QString());
emit closed();
return true;
} else if (err == "NO_CHAT_TITLE") {

View File

@@ -118,7 +118,7 @@ AddParticipantInner::ContactData *AddParticipantInner::contactData(DialogRow *ro
data->inchat = _chat->participants.constFind(user) != _chat->participants.cend();
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}
@@ -231,6 +231,7 @@ void AddParticipantInner::mousePressEvent(QMouseEvent *e) {
}
void AddParticipantInner::chooseParticipant() {
_time = unixtime();
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, from;
if (_filter.isEmpty()) {
if (!_sel || contactData(_sel)->inchat) return;
@@ -293,6 +294,7 @@ void AddParticipantInner::updateSel() {
}
void AddParticipantInner::updateFilter(QString filter) {
_time = unixtime();
QStringList f;
if (!filter.isEmpty()) {
QStringList filterList = filter.split(cWordSplit(), QString::SkipEmptyParts);
@@ -405,6 +407,7 @@ AddParticipantInner::~AddParticipantInner() {
}
void AddParticipantInner::selectSkip(int32 dir) {
_time = unixtime();
_mouseSel = false;
int32 rh = st::profileListPhotoSize + st::profileListPadding.height() * 2, origDir = dir;
if (_filter.isEmpty()) {

View File

@@ -205,6 +205,7 @@ void ConnectionBox::onSave() {
}
App::writeConfig();
MTP::restart();
reinitImageLinkManager();
emit closed();
}

View File

@@ -98,7 +98,7 @@ ContactsInner::ContactData *ContactsInner::contactData(DialogRow *row) {
if (i == _contactsData.cend()) {
_contactsData.insert(user, data = new ContactData());
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@@ -99,7 +99,7 @@ NewGroupInner::ContactData *NewGroupInner::contactData(DialogRow *row) {
_contactsData.insert(user, data = new ContactData());
data->check = false;
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
data->online = App::onlineText(user->onlineTill, _time);
data->online = App::onlineText(user, _time);
} else {
data = i.value();
}

View File

@@ -17,8 +17,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
*/
#pragma once
static const int32 AppVersion = 6007;
static const wchar_t *AppVersionStr = L"0.6.7";
static const int32 AppVersion = 6014;
static const wchar_t *AppVersionStr = L"0.6.14";
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
static const wchar_t *AppName = L"Telegram Desktop";
@@ -37,6 +37,7 @@ enum {
MTPAckSendWaiting = 10000, // how much time to wait for some more requests, when sending msg acks
MTPResendThreshold = 1, // how much ints should message contain for us not to resend, but to check it's state
MTPContainerLives = 600, // container lives 10 minutes in haveSent map
MTPMinReceiveDelay = 4000, // 4 seconds
MTPMaxReceiveDelay = 64000, // 64 seconds
MTPConnectionOldTimeout = 192000, // 192 seconds
MTPTcpConnectionWaitTimeout = 3000, // 3 seconds waiting for tcp, until we accept http
@@ -50,7 +51,6 @@ enum {
MTPDebugBufferSize = 1024 * 1024, // 1 mb start size
MinReceiveDelay = 1000, // 1 seconds
MaxSelectedItems = 100,
MaxPhoneTailLength = 18, // rest of the phone number, without country code (seen 12 at least)
@@ -100,8 +100,17 @@ enum {
UsernameCheckTimeout = 200,
MaxMessageSize = 4096,
MaxHttpRedirects = 5, // when getting external data/images
WriteMapTimeout = 1000,
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
};
inline bool isServiceUser(uint64 id) {
return (id == 333000) || (id == 777000);
}
#ifdef Q_OS_WIN
inline const GUID &cGUID() {
static const GUID gGuid = { 0x87a94ab0, 0xe370, 0x4cde, { 0x98, 0xd3, 0xac, 0xc1, 0x10, 0xc5, 0x96, 0x7d } };
@@ -234,7 +243,8 @@ enum {
UploadRequestInterval = 500, // one part each half second, if not uploaded faster
MaxPhotosInMemory = 50, // try to clear some memory after 50 photos are created
NoUpdatesTimeout = 180 * 1000, // if nothing is received in 3 min we reconnect
NoUpdatesTimeout = 180 * 1000, // if nothing is received in 3 min we getDifference
NoUpdatesAfterSleepTimeout = 60 * 1000, // if nothing is received in 1 min when was a sleepmode we getDifference
WaitForSeqTimeout = 1000, // 1s wait for skipped seq in updates
MemoryForImageCache = 64 * 1024 * 1024, // after 64mb of unpacked images we try to clear some memory

View File

@@ -1212,7 +1212,7 @@ void DialogsWidget::setInnerFocus() {
}
void DialogsWidget::regTyping(History *history, UserData *user) {
uint64 ms = getms();
uint64 ms = getms(true);
history->typing[user] = ms + 6000;
Histories::TypingHistories::const_iterator i = App::histories().typing.find(history);
@@ -1226,7 +1226,7 @@ void DialogsWidget::regTyping(History *history, UserData *user) {
}
bool DialogsWidget::animStep(float64) {
uint64 ms = getms();
uint64 ms = getms(true);
Histories::TypingHistories &typing(App::histories().typing);
for (Histories::TypingHistories::iterator i = typing.begin(), e = typing.end(); i != e;) {
uint32 typingFrame = (ms - i.value()) / 150;

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "style.h"
#include "flatinput.h"
#include "window.h"
namespace {
class FlatInputStyle : public QCommonStyle {
@@ -57,6 +58,7 @@ FlatInput::FlatInput(QWidget *parent, const style::flatInput &st, const QString
connect(this, SIGNAL(textChanged(const QString &)), this, SLOT(onTextChange(const QString &)));
connect(this, SIGNAL(textEdited(const QString &)), this, SLOT(onTextEdited()));
if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
setStyle(&_flatInputStyle);
setTextMargins(0, 0, 0, 0);
@@ -262,10 +264,12 @@ void FlatInput::onTextEdited() {
_oldtext = text();
if (was != _oldtext) emit changed();
updatePlaceholder();
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatInput::onTextChange(const QString &text) {
_oldtext = text;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatInput::notaBene() {

View File

@@ -19,11 +19,12 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "style.h"
#include "flattextarea.h"
#include "window.h"
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(v, parent),
_ph(pholder), _oldtext(v), _phVisible(!v.length()),
a_phLeft(_phVisible ? 0 : st.phShift), a_phAlpha(_phVisible ? 1 : 0), a_phColor(st.phColor->c),
_st(st), _fakeMargin(0),
_st(st), _undoAvailable(false), _redoAvailable(false), _fakeMargin(0),
_touchPress(false), _touchRightButton(false), _touchMove(false), _replacingEmojis(false) {
setAcceptRichText(false);
resize(_st.width, _st.font->height);
@@ -58,6 +59,9 @@ FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const
connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(onDocumentContentsChange(int, int, int)));
connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged()));
connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool)));
connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool)));
if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
}
void FlatTextarea::onTouchTimer() {
@@ -199,7 +203,7 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
QString t(fragment.text());
if (!full) {
if (p < start) {
t = t.mid(start - p, end - start - p);
t = t.mid(start - p, end - start);
} else if (e > end) {
t = t.mid(0, end - p);
}
@@ -223,7 +227,7 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
uint32 index = imageName.mid(8).toUInt(0, 16);
const EmojiData *emoji = getEmoji(index);
if (emoji) {
emojiText.reserve(emoji->len);
emojiText.reserve(emoji->len + (emoji->postfix ? 1 : 0));
switch (emoji->len) {
case 1: emojiText.append(QChar(emoji->code & 0xFFFF)); break;
case 2:
@@ -237,6 +241,7 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
emojiText.append(QChar(emoji->code2 & 0xFFFF));
break;
}
if (emoji->postfix) emojiText.append(QChar(emoji->postfix));
}
}
}
@@ -268,6 +273,14 @@ bool FlatTextarea::hasText() const {
return (from.next() != till);
}
bool FlatTextarea::isUndoAvailable() const {
return _undoAvailable;
}
bool FlatTextarea::isRedoAvailable() const {
return _redoAvailable;
}
void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
c.removeSelectedText();
@@ -283,7 +296,7 @@ void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
}
void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
int32 emojiPosition = 0;
int32 emojiPosition = 0, emojiLen = 0;
const EmojiData *emoji = 0;
QTextDocument *doc(document());
@@ -305,13 +318,14 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
QString t(fragment.text());
for (const QChar *ch = t.constData(), *e = ch + t.size(); ch != e; ++ch) {
if (ch + 1 < e && (ch->isHighSurrogate() || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) || (ch + 1)->unicode() == 0xFE0F)) {
if (ch + 1 < e && (ch->isHighSurrogate() || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3))) {
emoji = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
if (emoji) {
if (emoji->len == 4 && (ch + 3 >= e || ((uint32((ch + 2)->unicode()) << 16) | uint32((ch + 3)->unicode())) != emoji->code2)) {
emoji = 0;
} else {
emojiPosition = p + (ch - t.constData());
emojiLen = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
break;
}
}
@@ -320,6 +334,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
emoji = getEmoji(ch->unicode());
if (emoji) {
emojiPosition = p + (ch - t.constData());
emojiLen = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
break;
}
}
@@ -330,7 +345,7 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
}
if (emoji) {
QTextCursor c(doc->docHandle(), emojiPosition);
c.setPosition(emojiPosition + emoji->len, QTextCursor::KeepAnchor);
c.setPosition(emojiPosition + emojiLen, QTextCursor::KeepAnchor);
int32 removedUpto = c.position();
insertEmoji(emoji, c);
@@ -398,6 +413,17 @@ void FlatTextarea::onDocumentContentsChanged() {
emit changed();
}
updatePlaceholder();
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatTextarea::onUndoAvailable(bool avail) {
_undoAvailable = avail;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
void FlatTextarea::onRedoAvailable(bool avail) {
_redoAvailable = avail;
if (App::wnd()) App::wnd()->updateGlobalMenu();
}
bool FlatTextarea::animStep(float64 ms) {

View File

@@ -51,6 +51,9 @@ public:
QString getText(int32 start = 0, int32 end = -1) const;
bool hasText() const;
bool isUndoAvailable() const;
bool isRedoAvailable() const;
public slots:
void onTouchTimer();
@@ -58,6 +61,9 @@ public slots:
void onDocumentContentsChange(int position, int charsRemoved, int charsAdded);
void onDocumentContentsChanged();
void onUndoAvailable(bool avail);
void onRedoAvailable(bool avail);
signals:
void changed();
@@ -82,6 +88,8 @@ private:
anim::cvalue a_phColor;
style::flatTextarea _st;
bool _undoAvailable, _redoAvailable;
int32 _fakeMargin;
QTimer _touchTimer;

View File

@@ -29,18 +29,9 @@ namespace {
return img;
}
typedef QMap<QByteArray, StorageImage*> StorageImages;
typedef QMap<StorageKey, StorageImage*> StorageImages;
StorageImages storageImages;
QByteArray storageKey(int32 dc, const int64 &volume, int32 local, const int64 &secret) {
QByteArray result(24, Qt::Uninitialized);
memcpy(result.data(), &dc, 4);
memcpy(result.data() + 4, &volume, 8);
memcpy(result.data() + 12, &local, 4);
memcpy(result.data() + 16, &secret, 8);
return result;
}
int64 globalAquiredSize = 0;
}
@@ -88,7 +79,7 @@ const QPixmap &Image::pixBlurred(int32 w, int32 h) const {
w *= cIntRetinaFactor();
h *= cIntRetinaFactor();
}
uint64 k = 0x8000000000000000L | (uint64(w) << 32) | uint64(h);
uint64 k = 0x8000000000000000LL | (uint64(w) << 32) | uint64(h);
Sizes::const_iterator i = _sizesCache.constFind(k);
if (i == _sizesCache.cend()) {
QPixmap p(pixBlurredNoCache(w, h));
@@ -101,6 +92,58 @@ const QPixmap &Image::pixBlurred(int32 w, int32 h) const {
return i.value();
}
const QPixmap &Image::pixSingle(int32 w, int32 h) const {
restore();
checkload();
if (w <= 0 || !width() || !height()) {
w = width() * cIntRetinaFactor();
} else if (cRetina()) {
w *= cIntRetinaFactor();
h *= cIntRetinaFactor();
}
uint64 k = 0LL;
Sizes::const_iterator i = _sizesCache.constFind(k);
if (i == _sizesCache.cend() || i->width() != w || (h && i->height() != h)) {
if (i != _sizesCache.cend()) {
globalAquiredSize -= int64(i->width()) * i->height() * 4;
}
QPixmap p(pixNoCache(w, h, true));
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
i = _sizesCache.insert(k, p);
if (!p.isNull()) {
globalAquiredSize += int64(p.width()) * p.height() * 4;
}
}
return i.value();
}
const QPixmap &Image::pixBlurredSingle(int32 w, int32 h) const {
restore();
checkload();
if (w <= 0 || !width() || !height()) {
w = width() * cIntRetinaFactor();
} else if (cRetina()) {
w *= cIntRetinaFactor();
h *= cIntRetinaFactor();
}
uint64 k = 0x8000000000000000LL | 0LL;
Sizes::const_iterator i = _sizesCache.constFind(k);
if (i == _sizesCache.cend() || i->width() != w || (h && i->height() != h)) {
if (i != _sizesCache.cend()) {
globalAquiredSize -= int64(i->width()) * i->height() * 4;
}
QPixmap p(pixBlurredNoCache(w, h));
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
i = _sizesCache.insert(k, p);
if (!p.isNull()) {
globalAquiredSize += int64(p.width()) * p.height() * 4;
}
}
return i.value();
}
namespace {
static inline uint64 _blurGetColors(const uchar *p) {
return p[0] + (p[1] << 16) + ((uint64)p[2] << 32);
@@ -428,7 +471,7 @@ bool StorageImage::loaded() const {
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageKey key(storageKey(dc, volume, local));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
i = storageImages.insert(key, new StorageImage(width, height, dc, volume, local, secret, size));
@@ -437,7 +480,7 @@ StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume,
}
StorageImage *getImage(int32 width, int32 height, int32 dc, const int64 &volume, int32 local, const int64 &secret, const QByteArray &bytes) {
QByteArray key(storageKey(dc, volume, local, secret));
StorageKey key(storageKey(dc, volume, local));
StorageImages::const_iterator i = storageImages.constFind(key);
if (i == storageImages.cend()) {
QByteArray bytesArr(bytes);

View File

@@ -34,6 +34,8 @@ public:
}
const QPixmap &pix(int32 w = 0, int32 h = 0) const;
const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const;
const QPixmap &pixSingle(int32 w = 0, int32 h = 0) const;
const QPixmap &pixBlurredSingle(int32 w = 0, int32 h = 0) const;
QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false) const;
QPixmap pixBlurredNoCache(int32 w, int32 h = 0) const;
@@ -51,6 +53,13 @@ public:
void forget() const;
void restore() const;
QByteArray savedFormat() const {
return format;
}
QByteArray savedData() const {
return saved;
}
virtual ~Image() {
invalidateSizeCache();
}
@@ -104,6 +113,24 @@ private:
LocalImage *getImage(const QString &file);
LocalImage *getImage(const QPixmap &pixmap, QByteArray format);
typedef QPair<uint64, uint64> StorageKey;
inline uint64 storageMix32To64(int32 a, int32 b) {
return (uint64(*reinterpret_cast<uint32*>(&a)) << 32) | uint64(*reinterpret_cast<uint32*>(&b));
}
inline StorageKey storageKey(int32 dc, const int64 &volume, int32 local) {
return StorageKey(storageMix32To64(dc, local), volume);
}
inline StorageKey storageKey(const MTPDfileLocation &location) {
return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v);
}
struct StorageImageSaved {
StorageImageSaved() : type(mtpc_storage_fileUnknown) {
}
StorageImageSaved(mtpTypeId type, const QByteArray &data) : type(type), data(data) {
}
mtpTypeId type;
QByteArray data;
};
class StorageImage : public Image {
public:
@@ -121,7 +148,7 @@ public:
void load(bool loadFirst = false, bool prior = true) {
if (loader) {
loader->start(loadFirst, prior);
check();
if (loader) check();
}
}
void checkload() const {
@@ -129,7 +156,7 @@ public:
if (!loader->loading()) {
loader->start(true);
}
check();
if (loader) check();
}
}

View File

@@ -563,12 +563,6 @@ public:
ch = *ptr;
chInt = (chInt << 16) | 0x20E3;
}
} else if (ptr + 1 < end && (ptr + 1)->unicode() == 0xFE0F) { // check for 32bit not surrogate emoji
_t->_text.push_back(ch);
skipBack = -1;
++ptr;
ch = *ptr;
chInt = (chInt << 16) | 0xFE0F;
}
lastSkipped = skip;
@@ -602,8 +596,13 @@ public:
_t->_text.push_back(*++ptr);
}
}
int emojiLen = e->len;
if (ptr + 1 < end && (ptr + 1)->unicode() == 0xFE0F) {
_t->_text.push_back(*++ptr);
++emojiLen;
}
createBlock(-e->len);
createBlock(-emojiLen);
emoji = e;
}
@@ -3975,7 +3974,7 @@ QString textAccentFold(const QString &text) {
continue;
}
if (ch->isHighSurrogate() && ch + 1 < e && (ch + 1)->isLowSurrogate()) {
QChar noAccent = QChar::surrogateToUcs4(*ch, *(ch + 1));
QChar noAccent = chNoAccent(QChar::surrogateToUcs4(*ch, *(ch + 1)));
if (noAccent.unicode() > 0) {
copying = true;
result[i] = noAccent;
@@ -4045,18 +4044,30 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) {
}
}
}
EmojiPtr e = 0;
if (ch->isHighSurrogate()) {
if (ch + 1 < end && (ch + 1)->isLowSurrogate()) {
++ch;
e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
if (!e) {
++ch;
}
}
} else {
if (ch + 1 < end && ((((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) || (ch + 1)->unicode() == 0xFE0F)) {
if (getEmoji((ch->unicode() << 16) | (ch + 1)->unicode())) {
++ch;
++s;
if (ch + 1 < end) {
if (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) {
e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode());
} else if ((ch + 1)->unicode() == 0xFE0F) {
e = getEmoji(ch->unicode());
}
}
}
if (e) {
ch += (e->len - 1);
if (ch + 1 < end && (ch + 1)->unicode() == 0xFE0F) {
++ch;
++s;
}
}
if (s >= limit) {
sendingText = leftText.mid(0, good - start);
leftText = leftText.mid(good - start);

File diff suppressed because it is too large Load Diff

View File

@@ -82,7 +82,7 @@ struct PeerData {
ChatData *asChat();
const ChatData *asChat() const;
void updateName(const QString &newName, const QString &newNameOrPhone);
void updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername);
void fillNames();
@@ -188,6 +188,9 @@ struct PhotoData {
ImagePtr full;
ChatData *chat; // for chat photos connection
// geo, caption
int32 cachew;
QPixmap cache;
};
class PhotoLink : public ITextLink {
@@ -581,6 +584,7 @@ enum HistoryMediaType {
MediaTypeContact,
MediaTypeAudio,
MediaTypeDocument,
MediaTypeImageLink,
MediaTypeCount
};
@@ -625,6 +629,34 @@ inline MTPMessagesFilter typeToMediaFilter(MediaOverviewType &type) {
return MTPMessagesFilter();
}
struct MessageCursor {
MessageCursor() : position(0), anchor(0), scroll(QFIXED_MAX) {
}
MessageCursor(int position, int anchor, int scroll) : position(position), anchor(anchor), scroll(scroll) {
}
MessageCursor(const QTextEdit &edit) {
fillFrom(edit);
}
void fillFrom(const QTextEdit &edit) {
QTextCursor c = edit.textCursor();
position = c.position();
anchor = c.anchor();
QScrollBar *s = edit.verticalScrollBar();
scroll = s ? s->value() : QFIXED_MAX;
}
void applyTo(QTextEdit &edit, bool *lock = 0) {
if (lock) *lock = true;
QTextCursor c = edit.textCursor();
c.setPosition(anchor, QTextCursor::MoveAnchor);
c.setPosition(position, QTextCursor::KeepAnchor);
edit.setTextCursor(c);
QScrollBar *s = edit.verticalScrollBar();
if (s) s->setValue(scroll);
if (lock) *lock = false;
}
int position, anchor, scroll;
};
class HistoryMedia;
class HistoryMessage;
class HistoryUnreadBar;
@@ -728,7 +760,7 @@ struct History : public QList<HistoryBlock*> {
}
QString draft;
QTextCursor draftCur;
MessageCursor draftCursor;
int32 lastWidth, lastScrollTop;
bool mute;
@@ -1214,12 +1246,20 @@ HistoryItem *regItem(HistoryItem *item, bool returnExisting = false);
class HistoryMedia : public HistoryElem {
public:
HistoryMedia(int32 width = 0) : w(width) {
}
virtual HistoryMediaType type() const = 0;
virtual const QString inDialogsText() const = 0;
virtual const QString inHistoryText() const = 0;
virtual bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0;
virtual int32 countHeight(const HistoryItem *parent, int32 width = -1) const {
return height();
}
virtual int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0) {
w = qMin(width, _maxw);
return _height;
}
virtual TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const = 0;
virtual void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const = 0;
virtual bool uploading() const {
@@ -1240,6 +1280,14 @@ public:
return false;
}
int32 currentWidth() const {
return w;
}
protected:
int32 w;
};
class HistoryPhoto : public HistoryMedia {
@@ -1257,6 +1305,7 @@ public:
return MediaTypePhoto;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
HistoryMedia *clone() const;
@@ -1265,6 +1314,8 @@ public:
return data;
}
void updateFrom(const MTPMessageMedia &media);
TextLinkPtr lnk() const {
return openl;
}
@@ -1277,7 +1328,7 @@ public:
private:
PhotoData *data;
TextLinkPtr openl;
int32 w;
};
QString formatSizeText(qint64 size);
@@ -1289,11 +1340,11 @@ public:
void initDimensions(const HistoryItem *parent);
void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const;
int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0);
HistoryMediaType type() const {
return MediaTypeVideo;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@@ -1307,8 +1358,7 @@ public:
private:
VideoData *data;
TextLinkPtr _openl, _savel, _cancell;
int32 w;
QString _size;
int32 _thumbw, _thumbx, _thumby;
@@ -1323,11 +1373,11 @@ public:
void initDimensions(const HistoryItem *parent);
void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const;
int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0);
HistoryMediaType type() const {
return MediaTypeAudio;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@@ -1341,7 +1391,6 @@ public:
private:
AudioData *data;
TextLinkPtr _openl, _savel, _cancell;
int32 w;
QString _size;
@@ -1361,6 +1410,7 @@ public:
return MediaTypeDocument;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
int32 countHeight(const HistoryItem *parent, int32 width = -1) const;
bool uploading() const {
@@ -1382,7 +1432,6 @@ private:
DocumentData *data;
TextLinkPtr _openl, _savel, _cancell;
int32 w;
int32 _namew;
QString _name, _size;
@@ -1404,6 +1453,7 @@ public:
return MediaTypeContact;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const;
HistoryMedia *clone() const;
@@ -1412,12 +1462,89 @@ public:
private:
int32 userId;
int32 w, phonew;
int32 phonew;
Text name;
QString phone;
UserData *contact;
};
void initImageLinkManager();
void reinitImageLinkManager();
void deinitImageLinkManager();
enum ImageLinkType {
InvalidImageLink = 0,
YouTubeLink,
VimeoLink,
InstagramLink,
GoogleMapsLink
};
struct ImageLinkData {
ImageLinkData(const QString &id) : id(id), type(InvalidImageLink), loading(false) {
}
QString id;
QString title, duration;
ImagePtr thumb;
ImageLinkType type;
bool loading;
void load();
};
class ImageLinkManager : public QObject {
Q_OBJECT
public:
ImageLinkManager() : manager(0), black(0) {
}
void init();
void reinit();
void deinit();
void getData(ImageLinkData *data);
~ImageLinkManager() {
deinit();
}
public slots:
void onFinished(QNetworkReply *reply);
void onFailed(QNetworkReply *reply);
private:
void failed(ImageLinkData *data);
QNetworkAccessManager *manager;
QMap<QNetworkReply*, ImageLinkData*> dataLoadings, imageLoadings;
QMap<ImageLinkData*, int32> serverRedirects;
ImagePtr *black;
};
class HistoryImageLink : public HistoryMedia {
public:
HistoryImageLink(const QString &url, int32 width = 0);
int32 fullWidth() const;
int32 fullHeight() const;
void initDimensions(const HistoryItem *parent);
void draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width = -1) const;
int32 resize(int32 width, bool dontRecountText = false, const HistoryItem *parent = 0);
HistoryMediaType type() const {
return MediaTypeImageLink;
}
const QString inDialogsText() const;
const QString inHistoryText() const;
bool hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
TextLinkPtr getLink(int32 x, int32 y, const HistoryItem *parent, int32 width = -1) const;
HistoryMedia *clone() const;
private:
ImageLinkData *data;
TextLinkPtr link;
};
class HistoryMessage : public HistoryItem {
public:

View File

@@ -28,6 +28,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "fileuploader.h"
#include "supporttl.h"
#include "localstorage.h"
// flick scroll taken from http://qt-project.org/doc/qt-4.8/demos-embedded-anomaly-src-flickcharm-cpp.html
HistoryList::HistoryList(HistoryWidget *historyWidget, ScrollArea *scroll, History *history) : QWidget(0)
@@ -633,106 +635,98 @@ void HistoryList::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
_contextMenuLnk = textlnkOver();
if (_contextMenuLnk && dynamic_cast<TextLink*>(_contextMenuLnk.data())) {
PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<EmailLink*>(_contextMenuLnk.data())) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<HashtagLink*>(_contextMenuLnk.data())) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true);
} else {
PhotoLink *lnkPhoto = dynamic_cast<PhotoLink*>(_contextMenuLnk.data());
VideoLink *lnkVideo = dynamic_cast<VideoLink*>(_contextMenuLnk.data());
AudioLink *lnkAudio = dynamic_cast<AudioLink*>(_contextMenuLnk.data());
DocumentLink *lnkDocument = dynamic_cast<DocumentLink*>(_contextMenuLnk.data());
if (lnkPhoto || lnkVideo || lnkAudio || lnkDocument) {
_menu = new ContextMenu(historyWidget);
if (isUponSelected > 0) {
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
}
if (lnkPhoto) {
_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
if (lnkPhoto) {
_menu->addAction(lang(lng_context_open_image), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_save_image), this, SLOT(saveContextImage()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_image), this, SLOT(copyContextImage()))->setEnabled(true);
} else {
if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else {
if ((lnkVideo && lnkVideo->video()->loader) || (lnkAudio && lnkAudio->audio()->loader) || (lnkDocument && lnkDocument->document()->loader)) {
_menu->addAction(lang(lng_context_cancel_download), this, SLOT(cancelContextDownload()))->setEnabled(true);
} else {
if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
}
_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_document)), this, SLOT(openContextFile()))->setEnabled(true);
_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_document)), this, SLOT(saveContextFile()))->setEnabled(true);
if ((lnkVideo && !lnkVideo->video()->already(true).isEmpty()) || (lnkAudio && !lnkAudio->audio()->already(true).isEmpty()) || (lnkDocument && !lnkDocument->document()->already(true).isEmpty())) {
_menu->addAction(lang(cPlatform() == dbipMac ? lng_context_show_in_finder : lng_context_show_in_folder), this, SLOT(showContextInFolder()))->setEnabled(true);
}
_menu->addAction(lang(lnkVideo ? lng_context_open_video : (lnkAudio ? lng_context_open_audio : lng_context_open_document)), this, SLOT(openContextFile()))->setEnabled(true);
_menu->addAction(lang(lnkVideo ? lng_context_save_video : (lnkAudio ? lng_context_save_audio : lng_context_save_document)), this, SLOT(saveContextFile()))->setEnabled(true);
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem())) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
App::contextItem(App::hoveredLinkItem());
}
} else { // maybe cursor on some text history item?
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
bool canDelete = (item && item->itemType() == HistoryItem::MsgType);
bool canForward = canDelete && (item->id > 0) && !item->serviceMsg();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
if (isUponSelected > 0) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
} else if (item && !isUponSelected && !_contextMenuLnk) {
QString contextMenuText = item->selectedText(FullItemSel);
if (!contextMenuText.isEmpty()) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}
if (isUponSelected > 1) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else {
if (!_menu) _menu = new ContextMenu(this);
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
}
App::contextItem(item);
}
if (isUponSelected > 1) {
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else if (App::hoveredLinkItem()) {
if (isUponSelected != -2) {
if (dynamic_cast<HistoryMessage*>(App::hoveredLinkItem())) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
App::contextItem(App::hoveredLinkItem());
}
} else { // maybe cursor on some text history item?
HistoryItem *item = App::hoveredItem() ? App::hoveredItem() : App::hoveredLinkItem();
bool canDelete = (item && item->itemType() == HistoryItem::MsgType);
bool canForward = canDelete && (item->id > 0) && !item->serviceMsg();
HistoryMessage *msg = dynamic_cast<HistoryMessage*>(item);
HistoryServiceMsg *srv = dynamic_cast<HistoryServiceMsg*>(item);
if (isUponSelected > 0) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_selected), this, SLOT(copySelectedText()))->setEnabled(true);
} else if (item && !isUponSelected && !_contextMenuLnk) {
QString contextMenuText = item->selectedText(FullItemSel);
if (!contextMenuText.isEmpty()) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}
if (_contextMenuLnk && dynamic_cast<TextLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_link), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_link), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<EmailLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_email), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_email), this, SLOT(copyContextUrl()))->setEnabled(true);
} else if (_contextMenuLnk && dynamic_cast<HashtagLink*>(_contextMenuLnk.data())) {
if (!_menu) _menu = new ContextMenu(historyWidget);
_menu->addAction(lang(lng_context_open_hashtag), this, SLOT(openContextUrl()))->setEnabled(true);
_menu->addAction(lang(lng_context_copy_hashtag), this, SLOT(copyContextUrl()))->setEnabled(true);
} else {
}
if (isUponSelected > 1) {
if (!_menu) _menu = new ContextMenu(this);
_menu->addAction(lang(lng_context_forward_selected), historyWidget, SLOT(onForwardSelected()));
_menu->addAction(lang(lng_context_delete_selected), historyWidget, SLOT(onDeleteSelected()));
_menu->addAction(lang(lng_context_clear_selection), historyWidget, SLOT(onClearSelected()));
} else {
if (!_menu) _menu = new ContextMenu(this);
if (isUponSelected != -2) {
if (canForward) {
_menu->addAction(lang(lng_context_forward_msg), historyWidget, SLOT(forwardMessage()))->setEnabled(true);
}
if (canDelete) {
_menu->addAction(lang((msg && msg->uploading()) ? lng_context_cancel_upload : lng_context_delete_msg), historyWidget, SLOT(deleteMessage()))->setEnabled(true);
}
}
_menu->addAction(lang(lng_context_select_msg), historyWidget, SLOT(selectMessage()))->setEnabled(true);
}
App::contextItem(item);
}
if (_menu) {
_menu->deleteOnHide();
connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroy(QObject*)));
@@ -976,6 +970,14 @@ HistoryItem *HistoryList::nextItem(HistoryItem *item) {
return 0;
}
bool HistoryList::canCopySelected() const {
return !_selected.isEmpty();
}
bool HistoryList::canDeleteSelected() const {
return !_selected.isEmpty() && (_selected.cbegin().value() == FullItemSel);
}
void HistoryList::getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const {
selectedForForward = selectedForDelete = 0;
for (SelectedItems::const_iterator i = _selected.cbegin(), e = _selected.cend(); i != e; ++i) {
@@ -1527,7 +1529,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
, _attachDragDocument(this)
, _attachDragPhoto(this)
, imageLoader(this)
, noTypingUpdate(false)
, _synthedTextUpdate(false)
, loadingChatId(0)
, loadingRequestId(0)
, serviceImageCacheSize(0)
@@ -1537,7 +1539,9 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
, bg(st::msgBG)
, hiderOffered(false)
, _scrollDelta(0)
, _typingRequest(0) {
, _typingRequest(0)
, _saveDraftStart(0)
, _saveDraftText(false) {
_scroll.setFocusPolicy(Qt::NoFocus);
setAcceptDrops(true);
@@ -1548,7 +1552,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
connect(&_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect()));
connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect()));
connect(&_field, SIGNAL(submitted(bool)), this, SLOT(onSend(bool)));
connect(&_field, SIGNAL(cancelled()), this, SIGNAL(cancelled()));
connect(&_field, SIGNAL(cancelled()), this, SLOT(onCancel()));
connect(&_field, SIGNAL(tabbed()), this, SLOT(onFieldTabbed()));
connect(&_field, SIGNAL(resized()), this, SLOT(onFieldResize()));
connect(&_field, SIGNAL(focused()), this, SLOT(onFieldFocused()));
@@ -1567,6 +1571,11 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
_animActiveTimer.setSingleShot(false);
connect(&_animActiveTimer, SIGNAL(timeout()), this, SLOT(onAnimActiveStep()));
_saveDraftTimer.setSingleShot(true);
connect(&_saveDraftTimer, SIGNAL(timeout()), this, SLOT(onDraftSave()));
connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed()));
connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onDraftSaveDelayed()));
_scroll.hide();
_scroll.move(0, 0);
@@ -1597,6 +1606,43 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent)
void HistoryWidget::onTextChange() {
updateTyping();
if (!hist || _synthedTextUpdate) return;
_saveDraftText = true;
onDraftSave(true);
}
void HistoryWidget::onDraftSaveDelayed() {
if (!hist || _synthedTextUpdate) return;
if (!_field.textCursor().anchor() && !_field.textCursor().position() && !_field.verticalScrollBar()->value()) {
if (!Local::hasDraftPositions(hist->peer->id)) return;
}
onDraftSave(true);
}
void HistoryWidget::onDraftSave(bool delayed) {
if (!hist) return;
if (delayed) {
uint64 ms = getms();
if (!_saveDraftStart) {
_saveDraftStart = ms;
return _saveDraftTimer.start(SaveDraftTimeout);
} else if (ms - _saveDraftStart < SaveDraftAnywayTimeout) {
return _saveDraftTimer.start(SaveDraftTimeout);
}
}
writeDraft();
}
void HistoryWidget::writeDraft(const QString *text, const MessageCursor *cursor) {
bool save = hist && (_saveDraftStart > 0);
_saveDraftStart = 0;
_saveDraftTimer.stop();
if (_saveDraftText) {
if (save) Local::writeDraft(hist->peer->id, text ? (*text) : _field.getText());
_saveDraftText = false;
}
if (save) Local::writeDraftPositions(hist->peer->id, cursor ? (*cursor) : MessageCursor(_field));
}
void HistoryWidget::cancelTyping() {
@@ -1607,8 +1653,8 @@ void HistoryWidget::cancelTyping() {
}
void HistoryWidget::updateTyping(bool typing) {
uint64 ms = getms() + 10000;
if (noTypingUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
uint64 ms = getms(true) + 10000;
if (_synthedTextUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
hist->myTyping = typing ? ms : 0;
cancelTyping();
@@ -1717,7 +1763,10 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
}
if (hist) {
hist->draft = _field.getText();
hist->draftCur = _field.textCursor();
hist->draftCursor.fillFrom(_field);
writeDraft(&hist->draft, &hist->draftCursor);
if (hist->readyForWork() && _scroll.scrollTop() + 1 <= _scroll.scrollTopMax()) {
hist->lastWidth = _list->width();
} else {
@@ -1765,10 +1814,7 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
App::mousedItem(0);
if (peer) {
App::forgetPhotos();
App::forgetVideos();
App::forgetAudios();
App::forgetDocuments();
App::forgetMedia();
serviceImageCacheSize = imageCacheSize();
MTP::clearLoaderPriorities();
histInputPeer = histPeer->input;
@@ -1798,13 +1844,19 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l
App::main()->peerUpdated(histPeer);
noTypingUpdate = true;
setFieldText(hist->draft);
_field.setFocus();
if (!hist->draft.isEmpty()) {
_field.setTextCursor(hist->draftCur);
setFieldText(hist->draft);
_field.setFocus();
hist->draftCursor.applyTo(_field, &_synthedTextUpdate);
} else {
QString draft = Local::readDraft(hist->peer->id);
setFieldText(draft);
_field.setFocus();
if (!draft.isEmpty()) {
MessageCursor cur = Local::readDraftPositions(hist->peer->id);
cur.applyTo(_field, &_synthedTextUpdate);
}
}
noTypingUpdate = false;
connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged()));
connect(&_scroll, SIGNAL(scrolled()), _list, SLOT(onUpdateSelected()));
@@ -2218,6 +2270,10 @@ void HistoryWidget::onSend(bool ctrlShiftEnter) {
App::main()->sendPreparedText(hist, prepareMessage(_field.getText()));
setFieldText(QString());
_saveDraftText = true;
_saveDraftStart = getms();
onDraftSave();
if (!_attachType.isHidden()) _attachType.hideStart();
if (!_emojiPan.isHidden()) _emojiPan.hideStart();
}
@@ -2683,7 +2739,7 @@ void HistoryWidget::updateOnlineDisplay(int32 x, int32 w) {
}
}
} else {
text = App::onlineText(histPeer->asUser()->onlineTill, t);
text = App::onlineText(histPeer->asUser(), t);
}
if (titlePeerText != text) {
titlePeerText = text;
@@ -3159,9 +3215,14 @@ void HistoryWidget::onFieldTabbed() {
}
void HistoryWidget::setFieldText(const QString &text) {
noTypingUpdate = true;
_synthedTextUpdate = true;
_field.setPlainText(text);
noTypingUpdate = false;
_synthedTextUpdate = false;
}
void HistoryWidget::onCancel() {
showPeer(0);
emit cancelled();
}
void HistoryWidget::peerUpdated(PeerData *data) {
@@ -3213,6 +3274,9 @@ void HistoryWidget::onDeleteSelectedSure() {
for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
i.value()->destroy();
}
if (App::main() && App::main()->peer() == peer()) {
App::main()->itemResized(0);
}
App::wnd()->hideLayer();
}
@@ -3226,6 +3290,9 @@ void HistoryWidget::onDeleteContextSure() {
MTP::send(MTPmessages_DeleteMessages(MTP_vector<MTPint>(1, MTP_int(item->id))));
}
item->destroy();
if (App::main() && App::main()->peer() == peer()) {
App::main()->itemResized(0);
}
App::wnd()->hideLayer();
}

View File

@@ -68,6 +68,9 @@ public:
void updateMsg(const HistoryItem *msg);
bool canCopySelected() const;
bool canDeleteSelected() const;
void getSelectionState(int32 &selectedForForward, int32 &selectedForDelete) const;
void clearSelectedItems(bool onlyTextSelection = false);
void fillSelectedItems(SelectedItemSet &sel, bool forDelete = true);
@@ -343,6 +346,8 @@ signals:
public slots:
void onCancel();
void peerUpdated(PeerData *data);
void cancelTyping();
@@ -391,6 +396,9 @@ public slots:
void onAnimActiveStep();
void onDraftSaveDelayed();
void onDraftSave(bool delayed = false);
private:
bool messagesFailed(const RPCError &error, mtpRequestId requestId);
@@ -399,6 +407,7 @@ private:
void addMessagesToBack(const QVector<MTPMessage> &messages);
void chatLoaded(const MTPmessages_ChatFull &res);
void writeDraft(const QString *text = 0, const MessageCursor *cursor = 0);
void setFieldText(const QString &text);
QStringList getMediasFromMime(const QMimeData *d);
@@ -435,7 +444,7 @@ private:
int32 _selCount; // < 0 - text selected, focus list, not _field
LocalImageLoader imageLoader;
bool noTypingUpdate;
bool _synthedTextUpdate;
PeerId loadingChatId;
mtpRequestId loadingRequestId;
@@ -465,5 +474,9 @@ private:
mtpRequestId _typingRequest;
QTimer _typingStopTimer;
uint64 _saveDraftStart;
bool _saveDraftText;
QTimer _saveDraftTimer;
};

View File

@@ -0,0 +1,988 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop 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://tdesktop.com
*/
#include "stdafx.h"
#include "localstorage.h"
namespace {
typedef quint64 FileKey;
static const char tdfMagic[] = { 'T', 'D', 'F', '$' };
static const int32 tdfMagicLen = sizeof(tdfMagic);
QString toFilePart(FileKey val) {
QString result;
result.reserve(0x10);
for (int32 i = 0; i < 0x10; ++i) {
uchar v = (val & 0x0F);
result.push_back((v < 0x0A) ? ('0' + v) : ('A' + (v - 0x0A)));
val >>= 4;
}
return result;
}
FileKey fromFilePart(const QString &val) {
FileKey result = 0;
int32 i = val.size();
if (i != 0x10) return 0;
while (i > 0) {
--i;
result <<= 4;
uint16 ch = val.at(i).unicode();
if (ch >= 'A' && ch <= 'F') {
result |= (ch - 'A') + 0x0A;
} else if (ch >= '0' && ch <= '9') {
result |= (ch - '0');
} else {
return 0;
}
}
return result;
}
QString _basePath;
bool _started = false;
_local_inner::Manager *_manager = 0;
bool _working() {
return _manager && !_basePath.isEmpty();
}
bool keyAlreadyUsed(QString &name) {
name += '0';
if (QFileInfo(name).exists()) return true;
name[name.size() - 1] = '1';
return QFileInfo(name).exists();
}
FileKey genKey() {
if (!_working()) return 0;
FileKey result;
QString path;
path.reserve(_basePath.size() + 0x11);
path += _basePath;
do {
result = MTP::nonce<FileKey>();
path.resize(_basePath.size());
path += toFilePart(result);
} while (keyAlreadyUsed(path));
return result;
}
void clearKey(const FileKey &key, bool safe = true) {
if (!_working()) return;
QString name;
name.reserve(_basePath.size() + 0x11);
name += _basePath;
name += toFilePart(key);
name += '0';
QFile::remove(name);
if (safe) {
name[name.size() - 1] = '1';
QFile::remove(name);
}
}
QByteArray _passKeySalt, _passKeyEncrypted;
mtpAuthKey _oldKey, _passKey, _localKey;
void createLocalKey(const QByteArray &pass, QByteArray *salt, mtpAuthKey *result) {
uchar key[LocalEncryptKeySize] = { 0 };
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
QByteArray newSalt;
if (!salt) {
newSalt.resize(LocalEncryptSaltSize);
memset_rand(newSalt.data(), newSalt.size());
salt = &newSalt;
cSetLocalSalt(newSalt);
}
PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key);
result->setKey(key);
}
struct FileReadDescriptor {
FileReadDescriptor() : version(0) {
}
int32 version;
QByteArray data;
QBuffer buffer;
QDataStream stream;
~FileReadDescriptor() {
if (version) {
stream.setDevice(0);
if (buffer.isOpen()) buffer.close();
buffer.setBuffer(0);
}
}
};
struct EncryptedDescriptor {
EncryptedDescriptor() {
}
EncryptedDescriptor(uint32 size) {
uint32 fullSize = sizeof(uint32) + size;
if (fullSize & 0x0F) fullSize += 0x10 - (fullSize & 0x0F);
data.reserve(fullSize);
data.resize(sizeof(uint32));
buffer.setBuffer(&data);
buffer.open(QIODevice::WriteOnly);
buffer.seek(sizeof(uint32));
stream.setDevice(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
}
QByteArray data;
QBuffer buffer;
QDataStream stream;
void finish() {
if (stream.device()) stream.setDevice(0);
if (buffer.isOpen()) buffer.close();
buffer.setBuffer(0);
}
~EncryptedDescriptor() {
finish();
}
};
struct FileWriteDescriptor {
FileWriteDescriptor(const FileKey &key, bool safe = true) : dataSize(0) {
init(toFilePart(key), safe);
}
FileWriteDescriptor(const QString &name, bool safe = true) : dataSize(0) {
init(name, safe);
}
void init(const QString &name, bool safe) {
if (!_working()) return;
// detect order of read attempts and file version
QString toTry[2];
toTry[0] = _basePath + name + '0';
if (safe) {
toTry[1] = _basePath + name + '1';
QFileInfo toTry0(toTry[0]);
QFileInfo toTry1(toTry[1]);
if (toTry0.exists()) {
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 > mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
qSwap(toTry[0], toTry[1]);
}
toDelete = toTry[1];
} else if (toTry1.exists()) {
toDelete = toTry[1];
}
}
file.setFileName(toTry[0]);
if (file.open(QIODevice::WriteOnly)) {
file.write(tdfMagic, tdfMagicLen);
qint32 version = AppVersion;
file.write((const char*)&version, sizeof(version));
stream.setDevice(&file);
stream.setVersion(QDataStream::Qt_5_1);
}
}
bool writeData(const QByteArray &data) {
if (!file.isOpen()) return false;
stream << data;
quint32 len = data.isNull() ? 0xffffffff : data.size();
if (QSysInfo::ByteOrder != QSysInfo::BigEndian) {
len = qbswap(len);
}
md5.feed(&len, sizeof(len));
md5.feed(data.constData(), data.size());
dataSize += sizeof(len) + data.size();
return true;
}
QByteArray prepareEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
data.finish();
QByteArray &toEncrypt(data.data);
// prepare for encryption
uint32 size = toEncrypt.size(), fullSize = size;
if (fullSize & 0x0F) {
fullSize += 0x10 - (fullSize & 0x0F);
toEncrypt.resize(fullSize);
memset_rand(toEncrypt.data() + size, fullSize - size);
}
*(uint32*)toEncrypt.data() = size;
QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData());
return encrypted;
}
bool writeEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
return writeData(prepareEncrypted(data, key));
}
void finish() {
if (!file.isOpen()) return;
stream.setDevice(0);
md5.feed(&dataSize, sizeof(dataSize));
qint32 version = AppVersion;
md5.feed(&version, sizeof(version));
md5.feed(tdfMagic, tdfMagicLen);
file.write((const char*)md5.result(), 0x10);
file.close();
if (!toDelete.isEmpty()) {
QFile::remove(toDelete);
}
}
QFile file;
QDataStream stream;
QString toDelete;
HashMd5 md5;
int32 dataSize;
~FileWriteDescriptor() {
finish();
}
};
bool readFile(FileReadDescriptor &result, const QString &name, bool safe = true) {
if (!_working()) return false;
// detect order of read attempts
QString toTry[2];
toTry[0] = _basePath + name + '0';
if (safe) {
QFileInfo toTry0(toTry[0]);
if (toTry0.exists()) {
toTry[1] = _basePath + name + '1';
QFileInfo toTry1(toTry[1]);
if (toTry1.exists()) {
QDateTime mod0 = toTry0.lastModified(), mod1 = toTry1.lastModified();
if (mod0 < mod1) {
qSwap(toTry[0], toTry[1]);
}
} else {
toTry[1] = QString();
}
} else {
toTry[0][toTry[0].size() - 1] = '1';
}
}
for (int32 i = 0; i < 2; ++i) {
QString fname(toTry[i]);
if (fname.isEmpty()) break;
QFile f(fname);
if (!f.open(QIODevice::ReadOnly)) {
DEBUG_LOG(("App Info: failed to open '%1' for reading").arg(name));
continue;
}
// check magic
char magic[tdfMagicLen];
if (f.read(magic, tdfMagicLen) != tdfMagicLen) {
DEBUG_LOG(("App Info: failed to read magic from '%1'").arg(name));
continue;
}
if (memcmp(magic, tdfMagic, tdfMagicLen)) {
DEBUG_LOG(("App Info: bad magic %1 in '%2'").arg(mb(magic, tdfMagicLen).str()).arg(name));
continue;
}
// read app version
qint32 version;
if (f.read((char*)&version, sizeof(version)) != sizeof(version)) {
DEBUG_LOG(("App Info: failed to read version from '%1'").arg(name));
continue;
}
if (version > AppVersion) {
DEBUG_LOG(("App Info: version too big %1 for '%2', my version %3").arg(version).arg(name).arg(AppVersion));
continue;
}
// read data
QByteArray bytes = f.read(f.size());
int32 dataSize = bytes.size() - 16;
if (dataSize < 0) {
DEBUG_LOG(("App Info: bad file '%1', could not read sign part").arg(name));
continue;
}
// check signature
HashMd5 md5;
md5.feed(bytes.constData(), dataSize);
md5.feed(&dataSize, sizeof(dataSize));
md5.feed(&version, sizeof(version));
md5.feed(magic, tdfMagicLen);
if (memcmp(md5.result(), bytes.constData() + dataSize, 16)) {
DEBUG_LOG(("App Info: bad file '%1', signature did not match").arg(name));
continue;
}
bytes.resize(dataSize);
result.data = bytes;
bytes = QByteArray();
result.version = version;
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
if ((i == 0 && !toTry[1].isEmpty()) || i == 1) {
QFile::remove(toTry[1 - i]);
}
return true;
}
return false;
}
bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const mtpAuthKey &key = _localKey) {
if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
return false;
}
uint32 fullLen = encrypted.size() - 16;
QByteArray decrypted;
decrypted.resize(fullLen);
const char *encryptedKey = encrypted.constData(), *encryptedData = encrypted.constData() + 16;
aesDecryptLocal(encryptedData, decrypted.data(), fullLen, &key, encryptedKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), encryptedKey, 16)) {
LOG(("App Error: bad decrypt key, data not decrypted"));
return false;
}
uint32 dataLen = *(const uint32*)decrypted.constData();
if (dataLen > uint32(decrypted.size()) || dataLen <= fullLen - 16 || dataLen < sizeof(uint32)) {
LOG(("App Error: bad decrypted part size: %1, fullLen: %2, decrypted size: %3").arg(dataLen).arg(fullLen).arg(decrypted.size()));
return false;
}
decrypted.resize(dataLen);
result.data = decrypted;
decrypted = QByteArray();
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.buffer.seek(sizeof(uint32)); // skip len
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
return true;
}
bool readEncryptedFile(FileReadDescriptor &result, const QString &name, bool safe = true) {
if (!readFile(result, name, safe)) {
return false;
}
QByteArray encrypted;
result.stream >> encrypted;
EncryptedDescriptor data;
if (!decryptLocal(data, encrypted)) {
result.stream.setDevice(0);
if (result.buffer.isOpen()) result.buffer.close();
result.buffer.setBuffer(0);
result.data = QByteArray();
result.version = 0;
return false;
}
result.stream.setDevice(0);
if (result.buffer.isOpen()) result.buffer.close();
result.buffer.setBuffer(0);
result.data = data.data;
result.buffer.setBuffer(&result.data);
result.buffer.open(QIODevice::ReadOnly);
result.buffer.seek(data.buffer.pos());
result.stream.setDevice(&result.buffer);
result.stream.setVersion(QDataStream::Qt_5_1);
return true;
}
enum { // Local Storage Keys
lskUserMap = 0,
lskDraft, // data: PeerId peer
lskDraftPosition, // data: PeerId peer
lskStorage, // data: StorageKey location
};
typedef QMap<PeerId, FileKey> DraftsMap;
DraftsMap _draftsMap, _draftsPositionsMap;
typedef QMap<PeerId, bool> DraftsNotReadMap;
DraftsNotReadMap _draftsNotReadMap;
typedef QPair<FileKey, qint32> FileDesc; // file, size
typedef QMap<StorageKey, FileDesc> StorageMap;
StorageMap _storageMap;
int32 _storageFilesSize = 0;
bool _mapChanged = false;
Local::ReadMapState _readMap(const QByteArray &pass) {
uint64 ms = getms();
QByteArray dataNameUtf8 = cDataFile().toUtf8();
uint64 dataNameHash[2];
hashMd5(dataNameUtf8.constData(), dataNameUtf8.size(), dataNameHash);
_basePath = cWorkingDir() + qsl("tdata/") + toFilePart(dataNameHash[0]) + QChar('/');
FileReadDescriptor mapData;
if (!readFile(mapData, qsl("map"))) {
return Local::ReadMapFailed;
}
QByteArray salt, keyEncrypted, mapEncrypted;
mapData.stream >> salt >> keyEncrypted >> mapEncrypted;
if (mapData.stream.status() != QDataStream::Ok) {
LOG(("App Error: could not read salt / key from map file - corrupted?..").arg(mapData.stream.status()));
return Local::ReadMapFailed;
}
if (salt.size() != LocalEncryptSaltSize) {
LOG(("App Error: bad salt in map file, size: %1").arg(salt.size()));
return Local::ReadMapFailed;
}
createLocalKey(pass, &salt, &_passKey);
EncryptedDescriptor keyData, map;
if (!decryptLocal(keyData, keyEncrypted, _passKey)) {
LOG(("App Error: could not decrypt pass-protected key from map file, maybe bad password.."));
return Local::ReadMapPassNeeded;
}
uchar key[LocalEncryptKeySize] = { 0 };
if (keyData.stream.readRawData((char*)key, LocalEncryptKeySize) != LocalEncryptKeySize || !keyData.stream.atEnd()) {
LOG(("App Error: could not read pass-protected key from map file"));
return Local::ReadMapFailed;
}
_localKey.setKey(key);
_passKeyEncrypted = keyEncrypted;
_passKeySalt = salt;
if (!decryptLocal(map, mapEncrypted)) {
LOG(("App Error: could not decrypt map."));
return Local::ReadMapFailed;
}
DraftsMap draftsMap, draftsPositionsMap;
DraftsNotReadMap draftsNotReadMap;
StorageMap storageMap;
qint64 storageFilesSize = 0;
while (!map.stream.atEnd()) {
quint32 keyType;
map.stream >> keyType;
switch (keyType) {
case lskDraft: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 p;
map.stream >> key >> p;
draftsMap.insert(p, key);
draftsNotReadMap.insert(p, true);
}
} break;
case lskDraftPosition: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 p;
map.stream >> key >> p;
draftsPositionsMap.insert(p, key);
}
} break;
case lskStorage: {
quint32 count = 0;
map.stream >> count;
for (quint32 i = 0; i < count; ++i) {
FileKey key;
quint64 first, second;
qint32 size;
map.stream >> key >> first >> second >> size;
storageMap.insert(StorageKey(first, second), FileDesc(key, size));
storageFilesSize += size;
}
} break;
default:
LOG(("App Error: unknown key type in encrypted map: %1").arg(keyType));
return Local::ReadMapFailed;
}
if (map.stream.status() != QDataStream::Ok) {
LOG(("App Error: reading encrypted map bad status: %1").arg(map.stream.status()));
return Local::ReadMapFailed;
}
}
_draftsMap = draftsMap;
_draftsPositionsMap = draftsPositionsMap;
_draftsNotReadMap = draftsNotReadMap;
_storageMap = storageMap;
_storageFilesSize = storageFilesSize;
_mapChanged = false;
LOG(("Map read time: %1").arg(getms() - ms));
return Local::ReadMapDone;
}
enum WriteMapWhen {
WriteMapNow,
WriteMapFast,
WriteMapSoon,
};
void _writeMap(WriteMapWhen when = WriteMapSoon) {
if (when != WriteMapNow) {
_manager->writeMap(when == WriteMapFast);
return;
}
_manager->writingMap();
if (!_mapChanged) return;
if (_basePath.isEmpty()) {
LOG(("App Error: _basePath is empty in writeMap()"));
return;
}
QDir().mkpath(_basePath);
FileWriteDescriptor map(qsl("map"));
if (_passKeySalt.isEmpty() || _passKeyEncrypted.isEmpty()) {
uchar local5Key[LocalEncryptKeySize] = { 0 };
QByteArray pass(LocalEncryptKeySize, Qt::Uninitialized), salt(LocalEncryptSaltSize, Qt::Uninitialized);
memset_rand(pass.data(), pass.size());
memset_rand(salt.data(), salt.size());
createLocalKey(pass, &salt, &_localKey);
_passKeySalt.resize(LocalEncryptSaltSize);
memset_rand(_passKeySalt.data(), _passKeySalt.size());
createLocalKey(QByteArray(), &_passKeySalt, &_passKey);
EncryptedDescriptor passKeyData(LocalEncryptKeySize);
_localKey.write(passKeyData.stream);
_passKeyEncrypted = map.prepareEncrypted(passKeyData, _passKey);
}
map.writeData(_passKeySalt);
map.writeData(_passKeyEncrypted);
uint32 mapSize = 0;
if (!_draftsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsMap.size() * sizeof(quint64) * 2;
if (!_draftsPositionsMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _draftsPositionsMap.size() * sizeof(quint64) * 2;
if (!_storageMap.isEmpty()) mapSize += sizeof(quint32) * 2 + _storageMap.size() * (sizeof(quint64) * 3 + sizeof(qint32));
EncryptedDescriptor mapData(mapSize);
if (!_draftsMap.isEmpty()) {
mapData.stream << quint32(lskDraft) << quint32(_draftsMap.size());
for (DraftsMap::const_iterator i = _draftsMap.cbegin(), e = _draftsMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value()) << quint64(i.key());
}
}
if (!_draftsPositionsMap.isEmpty()) {
mapData.stream << quint32(lskDraftPosition) << quint32(_draftsPositionsMap.size());
for (DraftsMap::const_iterator i = _draftsPositionsMap.cbegin(), e = _draftsPositionsMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value()) << quint64(i.key());
}
}
if (!_storageMap.isEmpty()) {
mapData.stream << quint32(lskStorage) << quint32(_storageMap.size());
for (StorageMap::const_iterator i = _storageMap.cbegin(), e = _storageMap.cend(); i != e; ++i) {
mapData.stream << quint64(i.value().first) << quint64(i.key().first) << quint64(i.key().second) << qint32(i.value().second);
}
}
map.writeEncrypted(mapData);
map.finish();
_mapChanged = false;
}
}
namespace _local_inner {
Manager::Manager() {
_mapWriteTimer.setSingleShot(true);
connect(&_mapWriteTimer, SIGNAL(timeout()), this, SLOT(mapWriteTimeout()));
}
void Manager::writeMap(bool fast) {
if (!_mapWriteTimer.isActive() || fast) {
_mapWriteTimer.start(fast ? 1 : WriteMapTimeout);
} else if (_mapWriteTimer.remainingTime() <= 0) {
mapWriteTimeout();
}
}
void Manager::writingMap() {
_mapWriteTimer.stop();
}
void Manager::mapWriteTimeout() {
_writeMap(WriteMapNow);
}
void Manager::finish() {
if (_mapWriteTimer.isActive()) {
mapWriteTimeout();
_mapWriteTimer.stop();
}
}
}
namespace Local {
mtpAuthKey &oldKey() {
return _oldKey;
}
void createOldKey(QByteArray *salt) {
createLocalKey(QByteArray(), salt, &_oldKey);
}
void start() {
if (!_started) {
_started = true;
_manager = new _local_inner::Manager();
}
}
void stop() {
if (_manager) {
_writeMap(WriteMapNow);
_manager->finish();
_manager->deleteLater();
_manager = 0;
}
}
ReadMapState readMap(const QByteArray &pass) {
ReadMapState result = _readMap(pass);
if (result == ReadMapFailed) {
_mapChanged = true;
_writeMap(WriteMapNow);
}
return result;
}
void writeDraft(const PeerId &peer, const QString &text) {
if (!_working()) return;
if (text.isEmpty()) {
DraftsMap::iterator i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) {
clearKey(i.value());
_draftsMap.erase(i);
_mapChanged = true;
_writeMap();
}
_draftsNotReadMap.remove(peer);
} else {
DraftsMap::const_iterator i = _draftsMap.constFind(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
QString to = _basePath + toFilePart(i.value());
EncryptedDescriptor data(sizeof(quint64) + sizeof(quint32) + text.size() * sizeof(QChar));
data.stream << quint64(peer) << text;
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
_draftsNotReadMap.remove(peer);
}
}
QString readDraft(const PeerId &peer) {
if (!_draftsNotReadMap.remove(peer)) return QString();
DraftsMap::iterator j = _draftsMap.find(peer);
if (j == _draftsMap.cend()) {
return QString();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value()))) {
clearKey(j.value());
_draftsMap.erase(j);
return QString();
}
quint64 draftPeer;
QString draftText;
draft.stream >> draftPeer >> draftText;
return (draftPeer == peer) ? draftText : QString();
}
void writeDraftPositions(const PeerId &peer, const MessageCursor &cur) {
if (!_working()) return;
if (cur.position == 0 && cur.anchor == 0 && cur.scroll == 0) {
DraftsMap::iterator i = _draftsPositionsMap.find(peer);
if (i != _draftsPositionsMap.cend()) {
clearKey(i.value());
_draftsPositionsMap.erase(i);
_mapChanged = true;
_writeMap();
}
} else {
DraftsMap::const_iterator i = _draftsPositionsMap.constFind(peer);
if (i == _draftsPositionsMap.cend()) {
i = _draftsPositionsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
QString to = _basePath + toFilePart(i.value());
EncryptedDescriptor data(sizeof(quint64) + sizeof(qint32) * 3);
data.stream << quint64(peer) << qint32(cur.position) << qint32(cur.anchor) << qint32(cur.scroll);
FileWriteDescriptor file(i.value());
file.writeEncrypted(data);
}
}
MessageCursor readDraftPositions(const PeerId &peer) {
DraftsMap::iterator j = _draftsPositionsMap.find(peer);
if (j == _draftsPositionsMap.cend()) {
return MessageCursor();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value()))) {
clearKey(j.value());
_draftsPositionsMap.erase(j);
return MessageCursor();
}
quint64 draftPeer;
qint32 curPosition, curAnchor, curScroll;
draft.stream >> draftPeer >> curPosition >> curAnchor >> curScroll;
return (draftPeer == peer) ? MessageCursor(curPosition, curAnchor, curScroll) : MessageCursor();
}
bool hasDraftPositions(const PeerId &peer) {
return (_draftsPositionsMap.constFind(peer) != _draftsPositionsMap.cend());
}
qint32 _storageImageSize(qint32 rawlen) {
// fulllen + storagekey + type + len + data
qint32 result = sizeof(uint32) + sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + rawlen;
if (result & 0x0F) result += 0x10 - (result & 0x0F);
result += tdfMagicLen + sizeof(qint32) + sizeof(quint32) + 0x10 + 0x10; // magic + version + len of encrypted + part of sha1 + md5
return result;
}
void writeImage(const StorageKey &location, const ImagePtr &image) {
if (image->isNull() || !image->loaded()) return;
if (_storageMap.constFind(location) != _storageMap.cend()) return;
QByteArray fmt = image->savedFormat();
mtpTypeId format = 0;
if (fmt == "JPG") {
format = mtpc_storage_fileJpeg;
} else if (fmt == "PNG") {
format = mtpc_storage_filePng;
} else if (fmt == "GIF") {
format = mtpc_storage_fileGif;
}
if (format) {
image->forget();
writeImage(location, StorageImageSaved(format, image->savedData()), false);
}
}
void writeImage(const StorageKey &location, const StorageImageSaved &image, bool overwrite) {
if (!_working()) return;
qint32 size = _storageImageSize(image.data.size());
StorageMap::const_iterator i = _storageMap.constFind(location);
if (i == _storageMap.cend()) {
i = _storageMap.insert(location, FileDesc(genKey(), size));
_storageFilesSize += size;
_mapChanged = true;
_writeMap();
} else if (!overwrite) {
return;
}
EncryptedDescriptor data(sizeof(quint64) * 2 + sizeof(quint32) + sizeof(quint32) + image.data.size());
data.stream << quint64(location.first) << quint64(location.second) << quint32(image.type) << image.data;
FileWriteDescriptor file(i.value().first, false);
file.writeEncrypted(data);
if (i.value().second != size) {
_storageFilesSize += size;
_storageFilesSize -= i.value().second;
_storageMap[location].second = size;
}
}
StorageImageSaved readImage(const StorageKey &location) {
StorageMap::iterator j = _storageMap.find(location);
if (j == _storageMap.cend()) {
return StorageImageSaved();
}
FileReadDescriptor draft;
if (!readEncryptedFile(draft, toFilePart(j.value().first), false)) {
clearKey(j.value().first, false);
_storageFilesSize -= j.value().second;
_storageMap.erase(j);
return StorageImageSaved();
}
QByteArray imageData;
quint64 locFirst, locSecond;
quint32 imageType;
draft.stream >> locFirst >> locSecond >> imageType >> imageData;
return (locFirst == location.first && locSecond == location.second) ? StorageImageSaved(imageType, imageData) : StorageImageSaved();
}
int32 hasImages() {
return _storageMap.size();
}
qint64 storageFilesSize() {
return _storageFilesSize;
}
struct ClearManagerData {
QThread *thread;
StorageMap images;
QMutex mutex;
QList<int> tasks;
bool working;
};
ClearManager::ClearManager() : data(new ClearManagerData()) {
data->thread = new QThread();
data->working = true;
}
bool ClearManager::addTask(int task) {
QMutexLocker lock(&data->mutex);
if (!data->working) return false;
if (!data->tasks.isEmpty() && (data->tasks.at(0) == ClearManagerAll)) return true;
if (task == ClearManagerAll) {
data->tasks.clear();
} else {
if (task & ClearManagerImages) {
if (data->images.isEmpty()) {
data->images = _storageMap;
} else {
for (StorageMap::const_iterator i = _storageMap.cbegin(), e = _storageMap.cend(); i != e; ++i) {
StorageKey k = i.key();
while (data->images.constFind(k) != data->images.cend()) {
++k.second;
}
data->images.insert(k, i.value());
}
}
_storageMap.clear();
_storageFilesSize = 0;
_mapChanged = true;
_writeMap();
}
for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
if (data->tasks.at(i) == task) return true;
}
}
data->tasks.push_back(task);
return true;
}
bool ClearManager::hasTask(ClearManagerTask task) {
QMutexLocker lock(&data->mutex);
if (data->tasks.isEmpty()) return false;
if (data->tasks.at(0) == ClearManagerAll) return true;
for (int32 i = 0, l = data->tasks.size(); i < l; ++i) {
if (data->tasks.at(i) == task) return true;
}
return false;
}
void ClearManager::start() {
moveToThread(data->thread);
connect(data->thread, SIGNAL(started()), this, SLOT(onStart()));
data->thread->start();
}
ClearManager::~ClearManager() {
data->thread->deleteLater();
delete data;
}
void ClearManager::onStart() {
while (true) {
int task = 0;
bool result = false;
StorageMap images;
{
QMutexLocker lock(&data->mutex);
if (data->tasks.isEmpty()) {
data->working = false;
break;
}
task = data->tasks.at(0);
images = data->images;
}
switch (task) {
case ClearManagerAll:
result = (QDir(cTempDir()).removeRecursively() && QDir(_basePath).removeRecursively());
break;
case ClearManagerDownloads:
result = QDir(cTempDir()).removeRecursively();
break;
case ClearManagerImages:
for (StorageMap::const_iterator i = images.cbegin(), e = images.cend(); i != e; ++i) {
clearKey(i.value().first, false);
}
result = true;
break;
}
{
QMutexLocker lock(&data->mutex);
if (data->tasks.at(0) == task) {
data->tasks.pop_front();
if (data->tasks.isEmpty()) {
data->working = false;
}
}
if (result) {
emit succeed(task, data->working ? 0 : this);
} else {
emit failed(task, data->working ? 0 : this);
}
if (!data->working) break;
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*
This file is part of Telegram Desktop,
an unofficial desktop 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://tdesktop.com
*/
#pragma once
#include "types.h"
namespace _local_inner {
class Manager : public QObject {
Q_OBJECT
public:
Manager();
void writeMap(bool fast);
void writingMap();
void finish();
public slots:
void mapWriteTimeout();
private:
QTimer _mapWriteTimer;
};
}
namespace Local {
mtpAuthKey &oldKey();
void createOldKey(QByteArray *salt = 0);
void start();
void stop();
enum ClearManagerTask {
ClearManagerAll = 0xFFFF,
ClearManagerDownloads = 0x01,
ClearManagerImages = 0x02,
};
struct ClearManagerData;
class ClearManager : public QObject {
Q_OBJECT
public:
ClearManager();
bool addTask(int task);
bool hasTask(ClearManagerTask task);
void start();
~ClearManager();
public slots:
void onStart();
signals:
void succeed(int task, void *manager);
void failed(int task, void *manager);
private:
ClearManagerData *data;
};
enum ReadMapState {
ReadMapFailed = 0,
ReadMapDone = 1,
ReadMapPassNeeded = 2,
};
ReadMapState readMap(const QByteArray &pass);
void writeDraft(const PeerId &peer, const QString &text);
QString readDraft(const PeerId &peer);
void writeDraftPositions(const PeerId &peer, const MessageCursor &cur);
MessageCursor readDraftPositions(const PeerId &peer);
bool hasDraftPositions(const PeerId &peer);
void writeImage(const StorageKey &location, const ImagePtr &img);
void writeImage(const StorageKey &location, const StorageImageSaved &jpeg, bool overwrite = true);
StorageImageSaved readImage(const StorageKey &location);
int32 hasImages();
qint64 storageFilesSize();
};

View File

@@ -73,7 +73,7 @@ void debugLogWrite(const char *file, int32 line, const QString &v) {
(*debugLogStream) << msg;
debugLogStream->flush();
#ifdef Q_OS_WIN
OutputDebugString(reinterpret_cast<const wchar_t *>(msg.utf16()));
// OutputDebugString(reinterpret_cast<const wchar_t *>(msg.utf16()));
#elif defined Q_OS_MAC
objc_outputDebugString(msg);
#elif defined Q_OS_LINUX && defined _DEBUG

View File

@@ -143,7 +143,7 @@ void TopBarWidget::enableShadow(bool enable) {
void TopBarWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
if (e->rect().top() < st::topBarHeight) {
if (e->rect().top() < st::topBarHeight) { // optimize shadow-only drawing
p.fillRect(QRect(0, 0, width(), st::topBarHeight), st::topBarBG->b);
if (_clearSelection.isHidden()) {
p.save();
@@ -154,8 +154,6 @@ void TopBarWidget::paintEvent(QPaintEvent *e) {
p.setPen(st::btnDefLink.color->p);
p.drawText(st::topBarSelectedPos.x(), st::topBarSelectedPos.y() + st::linkFont->ascent, _selStr);
}
} else {
int a = 0; // optimize shadow-only drawing
}
if (_drawShadow) {
p.fillRect(st::titleShadow, st::topBarHeight, width() - st::titleShadow, st::titleShadow, st::titleShadowColor->b);
@@ -275,7 +273,7 @@ MainWidget *TopBarWidget::main() {
MainWidget::MainWidget(Window *window) : QWidget(window), failedObjId(0), _dialogsWidth(st::dlgMinWidth),
dialogs(this), history(this), profile(0), overview(0), _topBar(this), hider(0), _mediaType(this), _mediaTypeMask(0),
updPts(0), updDate(0), updQts(-1), updSeq(0), updInited(false), onlineRequest(0), _failDifferenceTimeout(1) {
updPts(0), updDate(0), updQts(-1), updSeq(0), updInited(false), onlineRequest(0), _failDifferenceTimeout(1), _lastUpdateTime(0) {
setGeometry(QRect(0, st::titleHeight, App::wnd()->width(), App::wnd()->height() - st::titleHeight));
connect(window, SIGNAL(resized(const QSize &)), this, SLOT(onParentResize(const QSize &)));
@@ -296,13 +294,6 @@ updPts(0), updDate(0), updQts(-1), updSeq(0), updInited(false), onlineRequest(0)
connect(audioVoice(), SIGNAL(stopped(AudioData*)), this, SLOT(audioPlayProgress(AudioData*)));
}
noUpdatesTimer.setSingleShot(true);
onlineTimer.setSingleShot(true);
onlineUpdater.setSingleShot(true);
updateNotifySettingTimer.setSingleShot(true);
_bySeqTimer.setSingleShot(true);
_failDifferenceTimer.setSingleShot(true);
dialogs.show();
history.show();
_topBar.hide();
@@ -722,7 +713,7 @@ void MainWidget::itemReplaced(HistoryItem *oldItem, HistoryItem *newItem) {
}
void MainWidget::itemResized(HistoryItem *row) {
if (history.peer() == row->history()->peer && !row->detached()) {
if (!row || (history.peer() == row->history()->peer && !row->detached())) {
history.itemResized(row);
}
if (overview) {
@@ -771,6 +762,17 @@ void MainWidget::peerUsernameChanged(PeerData *peer) {
}
}
void MainWidget::checkLastUpdate(bool afterSleep) {
uint64 n = getms(true);
if (_lastUpdateTime && n > _lastUpdateTime + (afterSleep ? NoUpdatesAfterSleepTimeout : NoUpdatesTimeout)) {
getDifference();
}
}
void MainWidget::showNewGroup() {
dialogs.onNewGroup();
}
void MainWidget::photosLoaded(History *h, const MTPmessages_Messages &msgs, mtpRequestId req) {
OverviewsPreload::iterator it;
MediaOverviewType type = OverviewCount;
@@ -1117,7 +1119,7 @@ void MainWidget::peerAfter(const PeerData *inPeer, MsgId inMsg, PeerData *&outPe
}
PeerData *MainWidget::peer() {
return history.peer();
return overview ? overview->peer() : history.peer();
}
PeerData *MainWidget::activePeer() {
@@ -1728,6 +1730,7 @@ void MainWidget::gotState(const MTPupdates_State &state) {
updSetState(d.vpts.v, d.vdate.v, d.vqts.v, d.vseq.v);
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
updInited = true;
@@ -1744,7 +1747,9 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) {
updSetState(updPts, d.vdate.v, updQts, d.vseq.v);
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
updInited = true;
} break;
case mtpc_updates_differenceSlice: {
@@ -1771,7 +1776,7 @@ void MainWidget::updUpdated(int32 pts, int32 seq) {
if (!updInited) return;
if (seq && (seq < updSeq || seq > updSeq + 1)) {
_bySeqPart.insert(seq, pts);
return _bySeqTimer.start();
return _bySeqTimer.start(WaitForSeqTimeout);
}
updSetState(pts, 0, 0, seq);
}
@@ -1800,6 +1805,7 @@ void MainWidget::getDifferenceForce() {
}
void MainWidget::getDifference() {
LOG(("Getting difference! no updates timer: %1, remains: %2").arg(noUpdatesTimer.isActive() ? 1 : 0).arg(noUpdatesTimer.remainingTime()));
if (!updInited) return;
_bySeqUpdates.clear();
@@ -1812,6 +1818,7 @@ void MainWidget::getDifference() {
noUpdatesTimer.stop();
_failDifferenceTimer.stop();
LOG(("Getting difference for %1, %2").arg(updPts).arg(updDate));
updInited = false;
MTP::setGlobalDoneHandler(RPCDoneHandlerPtr(0));
MTP::send(MTPupdates_GetDifference(MTP_int(updPts), MTP_int(updDate), MTP_int(updQts)), rpcDone(&MainWidget::gotDifference), rpcFail(&MainWidget::failDifference));
@@ -1990,6 +1997,8 @@ int32 MainWidget::dlgsWidth() const {
}
MainWidget::~MainWidget() {
if (App::main() == this) history.showPeer(0, 0, true);
delete hider;
MTP::clearGlobalHandlers();
App::deinitMedia(false);
@@ -2031,6 +2040,7 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
try {
MTPUpdates updates(from, end);
_lastUpdateTime = getms(true);
noUpdatesTimer.start(NoUpdatesTimeout);
handleUpdates(updates);
@@ -2243,6 +2253,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
if (user) {
switch (d.vstatus.type()) {
case mtpc_userStatusEmpty: user->onlineTill = 0; break;
case mtpc_userStatusRecently: user->onlineTill = -2; break;
case mtpc_userStatusLastWeek: user->onlineTill = -3; break;
case mtpc_userStatusLastMonth: user->onlineTill = -4; break;
case mtpc_userStatusOffline: user->onlineTill = d.vstatus.c_userStatusOffline().vwas_online.v; break;
case mtpc_userStatusOnline: user->onlineTill = d.vstatus.c_userStatusOnline().vexpires.v; break;
}
@@ -2254,8 +2267,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
case mtpc_updateUserName: {
const MTPDupdateUserName &d(update.c_updateUserName());
UserData *user = App::userLoaded(d.vuser_id.v);
if (user && user->contact <= 0) {
user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
if (user) {
if (user->contact <= 0) {
user->setName(textOneLine(qs(d.vfirst_name)), textOneLine(qs(d.vlast_name)), user->nameOrPhone, textOneLine(qs(d.vusername)));
} else {
user->setName(textOneLine(user->firstName), textOneLine(user->lastName), user->nameOrPhone, textOneLine(qs(d.vusername)));
}
if (App::main()) App::main()->peerUpdated(user);
}
} break;

View File

@@ -284,6 +284,9 @@ public:
void loadMediaBack(PeerData *peer, MediaOverviewType type, bool many = false);
void peerUsernameChanged(PeerData *peer);
void checkLastUpdate(bool afterSleep);
void showNewGroup();
~MainWidget();
signals:
@@ -380,14 +383,14 @@ private:
int updPts, updDate, updQts, updSeq;
bool updInited;
QTimer noUpdatesTimer;
SingleTimer noUpdatesTimer;
mtpRequestId onlineRequest;
QTimer onlineTimer;
QTimer onlineUpdater;
SingleTimer onlineTimer;
SingleTimer onlineUpdater;
QSet<PeerData*> updateNotifySettingPeers;
QTimer updateNotifySettingTimer;
SingleTimer updateNotifySettingTimer;
typedef QMap<PeerData*, mtpRequestId> ReadRequests;
ReadRequests _readRequests;
@@ -400,8 +403,10 @@ private:
QMap<int32, MTPmessages_StatedMessage> _bySeqStatedMessage;
QMap<int32, MTPmessages_StatedMessages> _bySeqStatedMessages;
QMap<int32, int32> _bySeqPart;
QTimer _bySeqTimer;
SingleTimer _bySeqTimer;
int32 _failDifferenceTimeout; // growing timeout for getDifference calls, if it fails
QTimer _failDifferenceTimer;
SingleTimer _failDifferenceTimer;
uint64 _lastUpdateTime;
};

View File

@@ -273,6 +273,7 @@ void MediaView::onDownload() {
if (cur.isEmpty()) {
_save.hide();
} else {
if (!QDir().exists(path)) QDir().mkpath(path);
toName = filedialogNextFilename(_doc->name, cur, path);
if (toName != cur && !QFile(cur).copy(toName)) {
toName = QString();
@@ -282,6 +283,7 @@ void MediaView::onDownload() {
if (!_photo || !_photo->full->loaded()) {
_save.hide();
} else {
if (!QDir().exists(path)) QDir().mkpath(path);
toName = filedialogDefaultName(qsl("photo"), qsl(".jpg"), path);
if (!_photo->full->pix().toImage().save(toName, "JPG")) {
toName = QString();
@@ -468,17 +470,11 @@ void MediaView::showPhoto(PhotoData *photo) {
_doc = 0;
_zoom = 0;
MTP::clearLoaderPriorities();
_photo->full->load();
_full = -1;
_current = QPixmap();
_down = OverNone;
_w = photo->full->width();
_h = photo->full->height();
switch (cScale()) {
case dbisOneAndQuarter: _w = qRound(float64(_w) * 1.25 - 0.01); _h = qRound(float64(_h) * 1.25 - 0.01); break;
case dbisOneAndHalf: _w = qRound(float64(_w) * 1.5 - 0.01); _h = qRound(float64(_h) * 1.5 - 0.01); break;
case dbisTwo: _w *= 2; _h *= 2; break;
}
_w = convertScale(photo->full->width());
_h = convertScale(photo->full->height());
if (isHidden()) {
moveToScreen();
}
@@ -495,6 +491,7 @@ void MediaView::showPhoto(PhotoData *photo) {
_width = _w;
_from = App::user(_photo->user);
updateControls();
_photo->full->load();
if (isHidden()) {
psUpdateOverlayed(this);
show();

View File

@@ -177,11 +177,11 @@ with open('scheme.tl') as f:
funcsText += '\tMTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n\t}\n';
funcsText += '\n';
funcsText += '\tuint32 size() const {\n'; # count size
funcsText += '\tuint32 innerLength() const {\n'; # count size
size = [];
for k in prmsList:
v = prms[k];
size.append('v' + k + '.size()');
size.append('v' + k + '.innerLength()');
if (not len(size)):
size.append('0');
funcsText += '\t\treturn ' + ' + '.join(size) + ';\n';
@@ -402,7 +402,7 @@ for restype in typesList:
writeText += '\t\t';
readText += '\tv.v' + paramName + '.read(from, end);\n';
writeText += '\tv.v' + paramName + '.write(to);\n';
sizeList.append('v.v' + paramName + '.size()');
sizeList.append('v.v' + paramName + '.innerLength()');
forwards += 'class MTPD' + name + ';\n'; # data class forward declaration
@@ -505,8 +505,8 @@ for restype in typesList:
if (withData):
typesText += getters;
typesText += '\n\tuint32 size() const;\n'; # size method
inlineMethods += '\ninline uint32 MTP' + restype + '::size() const {\n';
typesText += '\n\tuint32 innerLength() const;\n'; # size method
inlineMethods += '\ninline uint32 MTP' + restype + '::innerLength() const {\n';
if (withType and sizeCases):
inlineMethods += '\tswitch (_type) {\n';
inlineMethods += sizeCases;

View File

@@ -18,6 +18,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "stdafx.h"
#include "mtp.h"
#include "localstorage.h"
namespace {
typedef QMap<int32, MTProtoSessionPtr> Sessions;
Sessions sessions;
@@ -61,8 +63,6 @@ namespace {
MTPSessionResetHandler sessionResetHandler = 0;
_mtp_internal::RequestResender *resender = 0;
mtpAuthKey _localKey;
void importDone(const MTPauth_Authorization &result, mtpRequestId req) {
QMutexLocker locker1(&requestByDCLock);
@@ -187,7 +187,7 @@ namespace {
if (!requestId) return false;
int32 secs = m.captured(1).toInt();
uint64 sendAt = getms() + secs * 1000 + 10;
uint64 sendAt = getms(true) + secs * 1000 + 10;
DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end();
for (; i != e; ++i) {
if (i->first == requestId) return true;
@@ -350,10 +350,6 @@ namespace _mtp_internal {
requestsByDC.remove(requestId);
}
uint32 getLayer() {
return layer;
}
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser) {
mtpRequestId res = reqid();
request->requestId = res;
@@ -379,20 +375,25 @@ namespace _mtp_internal {
return req;
}
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent) {
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest) {
mtpMsgId afterId(*(mtpMsgId*)(from->after->data() + 4));
mtpRequestMap::const_iterator i = afterId ? haveSent.constFind(afterId) : haveSent.cend();
int32 size = to->size(), len = (*from)[7] >> 2, headlen = 4, fulllen = headlen + len;
int32 size = to->size(), lenInInts = (from.innerLength() >> 2), headlen = 4, fulllen = headlen + lenInInts;
if (i == haveSent.constEnd()) { // no invoke after or such msg was not sent or was completed recently
to->resize(size + fulllen);
memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime));
to->resize(size + fulllen + skipBeforeRequest);
if (skipBeforeRequest) {
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
memcpy(to->data() + size + headlen + skipBeforeRequest, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
} else {
memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime));
}
} else {
to->resize(size + fulllen + 3);
to->resize(size + fulllen + skipBeforeRequest + 3);
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
(*to)[size + 3] += 3 * sizeof(mtpPrime);
*((mtpTypeId*)&((*to)[size + headlen])) = mtpc_invokeAfterMsg;
memcpy(to->data() + size + headlen + 1, &afterId, 2 * sizeof(mtpPrime));
memcpy(to->data() + size + headlen + 3, from->constData() + 4 + headlen, len * sizeof(mtpPrime));
*((mtpTypeId*)&((*to)[size + headlen + skipBeforeRequest])) = mtpc_invokeAfterMsg;
memcpy(to->data() + size + headlen + skipBeforeRequest + 1, &afterId, 2 * sizeof(mtpPrime));
memcpy(to->data() + size + headlen + skipBeforeRequest + 3, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
if (size + 3 != 7) (*to)[7] += 3 * sizeof(mtpPrime);
}
}
@@ -521,8 +522,12 @@ namespace _mtp_internal {
return true;
}
RequestResender::RequestResender() {
connect(&_timer, SIGNAL(timeout()), this, SLOT(checkDelayed()));
}
void RequestResender::checkDelayed() {
uint64 now = getms();
uint64 now = getms(true);
while (!delayedRequests.isEmpty() && now >= delayedRequests.front().second) {
mtpRequestId requestId = delayedRequests.front().first;
delayedRequests.pop_front();
@@ -553,37 +558,17 @@ namespace _mtp_internal {
}
if (!delayedRequests.isEmpty()) {
QTimer::singleShot(delayedRequests.front().second - now, this, SLOT(checkDelayed()));
_timer.start(delayedRequests.front().second - now);
}
}
};
namespace MTP {
mtpAuthKey &localKey() {
return _localKey;
}
void createLocalKey(const QByteArray &pass, QByteArray *salt) {
uchar key[LocalEncryptKeySize] = { 0 };
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
QByteArray newSalt;
if (!salt) {
newSalt.resize(LocalEncryptSaltSize);
memset_rand(newSalt.data(), newSalt.size());
salt = &newSalt;
cSetLocalSalt(newSalt);
}
PKCS5_PBKDF2_HMAC_SHA1(pass.constData(), pass.size(), (uchar*)salt->data(), salt->size(), iterCount, LocalEncryptKeySize, key);
_localKey.setKey(key);
}
void start() {
unixtimeInit();
if (!localKey().created()) {
if (!Local::oldKey().created()) {
LOG(("App Error: trying to start MTP without local key!"));
return;
}
@@ -620,15 +605,6 @@ namespace MTP {
}
}
void setLayer(uint32 l) {
if (l > mtpLayerMax) {
l = mtpLayerMax;
} else if (!l) {
l = 1;
}
layer = l - 1;
}
void setdc(int32 dc, bool fromZeroOnly) {
if (!started) return;
@@ -701,8 +677,16 @@ namespace MTP {
void killSession(int32 dc) {
Sessions::iterator i = sessions.find(dc);
if (i != sessions.end()) {
bool wasMain = (i.value() == mainSession);
i.value()->stop();
sessions.erase(i);
if (wasMain) {
mainSession = MTProtoSessionPtr(new MTProtoSession());
mainSession->start(mtpMainDC());
sessions[mainSession->getDC()] = mainSession;
}
}
}
@@ -722,6 +706,8 @@ namespace MTP {
for (Sessions::iterator i = sessions.begin(), e = sessions.end(); i != e; ++i) {
i.value()->stop();
}
sessions.clear();
mainSession = MTProtoSessionPtr();
delete resender;
resender = 0;
mtpDestroyConfigLoader();

View File

@@ -26,13 +26,11 @@ namespace _mtp_internal {
void registerRequest(mtpRequestId requestId, int32 dc);
void unregisterRequest(mtpRequestId requestId);
uint32 getLayer();
static const uint32 dcShift = 10000;
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser);
mtpRequest getRequest(mtpRequestId req);
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent);
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest = 0);
void clearCallbacks(mtpRequestId requestId, int32 errorCode = RPCError::NoError); // 0 - do not toggle onError callback
void clearCallbacksDelayed(const RPCCallbackClears &requestIds);
void performDelayedClear();
@@ -49,17 +47,22 @@ namespace _mtp_internal {
class RequestResender : public QObject {
Q_OBJECT
public:
RequestResender();
public slots:
void checkDelayed();
private:
SingleTimer _timer;
};
};
namespace MTP {
mtpAuthKey &localKey();
void createLocalKey(const QByteArray &pass, QByteArray *salt = 0);
static const uint32 cfg = 1 * _mtp_internal::dcShift; // send(MTPhelp_GetConfig(), MTP::cfg + dc) - for dc enum
static const uint32 dld[MTPDownloadSessionsCount] = { // send(req, callbacks, MTP::dld[i] + dc) - for download
0x10 * _mtp_internal::dcShift,
@@ -78,8 +81,6 @@ namespace MTP {
void restart();
void restart(int32 dcMask);
void setLayer(uint32 layer);
void setdc(int32 dc, bool fromZeroOnly = false);
int32 maindc();
int32 dcstate(int32 dc = 0);
@@ -87,7 +88,10 @@ namespace MTP {
void initdc(int32 dc);
template <typename TRequest>
inline mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {
return _mtp_internal::getSession(dc)->send(request, callbacks, msCanWait, _mtp_internal::getLayer(), !dc, after);
MTProtoSessionPtr session = _mtp_internal::getSession(dc);
if (!session) return 0;
return session->send(request, callbacks, msCanWait, true, !dc, after);
}
template <typename TRequest>
inline mtpRequestId send(const TRequest &request, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail = RPCFailHandlerPtr(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {

View File

@@ -48,7 +48,7 @@ public:
return _keyId;
}
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) {
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) const {
if (!_isset) throw mtpErrorKeyNotReady(QString("prepareAES(.., %1)").arg(logBool(send)));
uint32 x = send ? 0 : 8;
@@ -112,14 +112,14 @@ inline void aesEncrypt(const void *src, void *dst, uint32 len, void *key, void *
AES_ige_encrypt((const uchar*)src, (uchar*)dst, len, &aes, aes_iv, AES_ENCRYPT);
}
inline void aesEncrypt(const void *src, void *dst, uint32 len, mtpAuthKeyPtr authKey, const MTPint128 &msgKey) {
inline void aesEncrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(msgKey, aesKey, aesIV);
return aesEncrypt(src, dst, len, &aesKey, &aesIV);
}
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, mtpAuthKey *authKey, const void *key128) {
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);
@@ -136,14 +136,14 @@ inline void aesDecrypt(const void *src, void *dst, uint32 len, void *key, void *
AES_ige_encrypt((const uchar*)src, (uchar*)dst, len, &aes, aes_iv, AES_DECRYPT);
}
inline void aesDecrypt(const void *src, void *dst, uint32 len, mtpAuthKeyPtr authKey, const MTPint128 &msgKey) {
inline void aesDecrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(msgKey, aesKey, aesIV, false);
return aesDecrypt(src, dst, len, &aesKey, &aesIV);
}
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, mtpAuthKey *authKey, const void *key128) {
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
MTPint256 aesKey, aesIV;
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);

View File

@@ -298,18 +298,19 @@ int32 MTProtoConnection::start(MTPSessionData *sessionData, int32 dc) {
dc = data->getDC();
if (!dc) {
delete data;
delete thread;
data = 0;
thread = 0;
return 0;
}
thread->start();
return dc;
}
void MTProtoConnection::restart() {
emit data->needToRestart();
}
void MTProtoConnection::stop() {
thread->quit();
if (data) data->stop();
if (thread) thread->quit();
}
void MTProtoConnection::stopped() {
@@ -317,6 +318,7 @@ void MTProtoConnection::stopped() {
if (data) data->deleteLater();
thread = 0;
data = 0;
delete this;
}
int32 MTProtoConnection::state() const {
@@ -472,7 +474,7 @@ namespace {
mtpBuffer _preparePQFake(const MTPint128 &nonce) {
MTPReq_pq req_pq(nonce);
mtpBuffer buffer;
uint32 requestSize = req_pq.size() >> 2;
uint32 requestSize = req_pq.innerLength() >> 2;
buffer.resize(0);
buffer.reserve(8 + requestSize);
@@ -603,7 +605,7 @@ void MTPabstractTcpConnection::socketRead() {
}
MTPautoConnection::MTPautoConnection(QThread *thread) : status(WaitingBoth),
tcpNonce(MTP::nonce<MTPint128>()), httpNonce(MTP::nonce<MTPint128>()) {
tcpNonce(MTP::nonce<MTPint128>()), httpNonce(MTP::nonce<MTPint128>()), _tcpTimeout(MTPMinReceiveDelay) {
moveToThread(thread);
manager.moveToThread(thread);
@@ -612,6 +614,9 @@ tcpNonce(MTP::nonce<MTPint128>()), httpNonce(MTP::nonce<MTPint128>()) {
httpStartTimer.moveToThread(thread);
httpStartTimer.setSingleShot(true);
connect(&httpStartTimer, SIGNAL(timeout()), this, SLOT(onHttpStart()));
tcpTimeoutTimer.moveToThread(thread);
tcpTimeoutTimer.setSingleShot(true);
connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer()));
sock.moveToThread(thread);
sock.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
@@ -626,7 +631,7 @@ void MTPautoConnection::onHttpStart() {
if (status == HttpReady) {
DEBUG_LOG(("Connection Info: Http-transport chosen by timer"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
}
}
@@ -637,13 +642,37 @@ void MTPautoConnection::onSocketConnected() {
DEBUG_LOG(("Connection Info: sending fake req_pq through tcp transport"));
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
tcpTimeoutTimer.start(_tcpTimeout);
tcpSend(buffer);
} else if (status == WaitingHttp || status == UsingHttp) {
sock.disconnect();
sock.disconnectFromHost();
}
}
void MTPautoConnection::onTcpTimeoutTimer() {
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2;
_tcpTimeout = -_tcpTimeout;
QAbstractSocket::SocketState state = sock.state();
if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) {
sock.disconnectFromHost();
} else if (state != QAbstractSocket::ClosingState) {
sock.connectToHost(QHostAddress(_addr), _port);
}
}
}
void MTPautoConnection::onSocketDisconnected() {
if (_tcpTimeout < 0) {
_tcpTimeout = -_tcpTimeout;
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
sock.connectToHost(QHostAddress(_addr), _port);
return;
}
}
if (status == WaitingBoth) {
status = WaitingHttp;
} else if (status == WaitingTcp || status == UsingTcp) {
@@ -679,15 +708,6 @@ void MTPautoConnection::tcpSend(mtpBuffer &buffer) {
TCP_LOG(("TCP Info: write %1 packet %2 bytes").arg(packetNum).arg(len));
sock.write((const char*)&buffer[0], len);
//int64 b = sock.bytesToWrite();
//if (b > 100000) {
// int a = 0;
//}
//sock.flush();
//int64 b2 = sock.bytesToWrite();
//if (b2 > 0) {
// TCP_LOG(("TCP Info: writing many, %1 left to write").arg(b2));
//}
}
void MTPautoConnection::httpSend(mtpBuffer &buffer) {
@@ -724,14 +744,17 @@ 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
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
_addr = addr;
_port = port;
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(_addr), _port);
mtpBuffer buffer(_preparePQFake(httpNonce));
DEBUG_LOG(("Connection Info: sending fake req_pq through http transport"));
httpSend(buffer);
sock.connectToHost(QHostAddress(addr), port);
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
}
bool MTPautoConnection::isConnected() {
@@ -765,7 +788,7 @@ void MTPautoConnection::requestFinished(QNetworkReply *reply) {
} else {
DEBUG_LOG(("Connection Info: Http-transport chosen by pq-response, awaited"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
}
}
@@ -785,7 +808,7 @@ void MTPautoConnection::requestFinished(QNetworkReply *reply) {
return;
}
bool mayBeBadKey = _handleHttpError(reply);
bool mayBeBadKey = _handleHttpError(reply) && _sentEncrypted;
if (status == WaitingBoth) {
status = WaitingTcp;
} else if (status == WaitingHttp || status == UsingHttp) {
@@ -801,14 +824,15 @@ void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
if (data.size() == 1) {
if (status == WaitingBoth) {
status = WaitingHttp;
sock.disconnect();
sock.disconnectFromHost();
} else if (status == HttpReady) {
DEBUG_LOG(("Connection Info: Http-transport chosen by bad tcp response, ready"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
} else if (status == WaitingTcp || status == UsingTcp) {
emit error(data[0] == -404);
bool mayBeBadKey = (data[0] == -404) && _sentEncrypted;
emit error(mayBeBadKey);
} else {
LOG(("Strange Tcp Error; status %1").arg(status));
}
@@ -816,6 +840,7 @@ void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
receivedQueue.push_back(data);
emit receivedData();
} else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) {
tcpTimeoutTimer.stop();
try {
MTPResPQ res_pq = _readPQFakeReply(data);
const MTPDresPQ &res_pq_data(res_pq.c_resPQ());
@@ -827,11 +852,11 @@ void MTPautoConnection::socketPacket(mtpPrime *packet, uint32 size) {
} catch (Exception &e) {
if (status == WaitingBoth) {
status = WaitingHttp;
sock.disconnect();
sock.disconnectFromHost();
} else if (status == HttpReady) {
DEBUG_LOG(("Connection Info: Http-transport chosen by bad tcp response, awaited"));
status = UsingHttp;
sock.disconnect();
sock.disconnectFromHost();
emit connected();
} else {
emit error();
@@ -906,14 +931,15 @@ void MTPtcpConnection::disconnectFromServer() {
}
void MTPtcpConnection::connectToServer(const QString &addr, int32 port) {
sock.connectToHost(QHostAddress(addr), port);
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(addr), port);
}
void MTPtcpConnection::socketPacket(mtpPrime *packet, uint32 size) {
mtpBuffer data = _handleTcpResponse(packet, size);
if (data.size() == 1) {
emit error(data[0] == -404);
bool mayBeBadKey = (data[0] == -404) && _sentEncrypted;
emit error(mayBeBadKey);
}
receivedQueue.push_back(data);
@@ -1001,7 +1027,7 @@ void MTPhttpConnection::requestFinished(QNetworkReply *reply) {
return;
}
bool mayBeBadKey = _handleHttpError(reply);
bool mayBeBadKey = _handleHttpError(reply) && _sentEncrypted;
emit error(mayBeBadKey);
}
@@ -1049,7 +1075,7 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
, conn(0)
, retryTimeout(1)
, oldConnection(true)
, receiveDelay(MinReceiveDelay)
, receiveDelay(MTPMinReceiveDelay)
, firstSentAt(-1)
, pingId(0)
, toSendPingId(0)
@@ -1082,22 +1108,20 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
connect(thread, SIGNAL(started()), this, SLOT(socketStart()));
connect(thread, SIGNAL(finished()), this, SLOT(doFinish()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
connect(&retryTimer, SIGNAL(timeout()), this, SLOT(retryByTimer()));
connect(&connCheckTimer, SIGNAL(timeout()), this, SLOT(onBadConnection()));
connect(&oldConnectionTimer, SIGNAL(timeout()), this, SLOT(onOldConnection()));
connect(sessionData->owner(), SIGNAL(authKeyCreated()), this, SLOT(updateAuthKey()));
connect(sessionData->owner(), SIGNAL(authKeyCreated()), this, SLOT(updateAuthKey()), Qt::QueuedConnection);
connect(this, SIGNAL(needToRestart()), this, SLOT(restartNow()));
connect(this, SIGNAL(needToReceive()), sessionData->owner(), SLOT(tryToReceive()));
connect(this, SIGNAL(stateChanged(qint32)), sessionData->owner(), SLOT(onConnectionStateChange(qint32)));
connect(sessionData->owner(), SIGNAL(needToSend()), this, SLOT(tryToSend()));
connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()));
oldConnectionTimer.setSingleShot(true);
connCheckTimer.setSingleShot(true);
retryTimer.setSingleShot(true);
connect(sessionData->owner(), SIGNAL(needToRestart()), this, SLOT(restartNow()), Qt::QueuedConnection);
connect(this, SIGNAL(needToReceive()), sessionData->owner(), SLOT(tryToReceive()), Qt::QueuedConnection);
connect(this, SIGNAL(stateChanged(qint32)), sessionData->owner(), SLOT(onConnectionStateChange(qint32)), Qt::QueuedConnection);
connect(sessionData->owner(), SIGNAL(needToSend()), this, SLOT(tryToSend()), Qt::QueuedConnection);
connect(this, SIGNAL(needToSendAsync()), sessionData->owner(), SIGNAL(needToSend()), Qt::QueuedConnection);
connect(this, SIGNAL(sendHttpWait()), sessionData->owner(), SLOT(sendHttpWait()), Qt::QueuedConnection);
connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()), Qt::QueuedConnection);
connect(this, SIGNAL(sendAnythingAsync(quint64)), sessionData->owner(), SLOT(sendAnything(quint64)), Qt::QueuedConnection);
}
void MTProtoConnectionPrivate::onConfigLoaded() {
@@ -1113,7 +1137,7 @@ int32 MTProtoConnectionPrivate::getState() const {
int32 result = _state;
if (_state < 0) {
if (retryTimer.isActive()) {
result = int32(getms() - retryWillFinish);
result = int32(getms(true) - retryWillFinish);
if (result >= 0) {
result = -1;
}
@@ -1140,7 +1164,7 @@ bool MTProtoConnectionPrivate::setState(int32 state, int32 ifState) {
if (state < 0) {
retryTimeout = -state;
retryTimer.start(retryTimeout);
retryWillFinish = getms() + retryTimeout;
retryWillFinish = getms(true) + retryTimeout;
}
emit stateChanged(state);
return true;
@@ -1360,8 +1384,12 @@ mtpMsgId MTProtoConnectionPrivate::placeToContainer(mtpRequest &toSendRequest, m
}
void MTProtoConnectionPrivate::tryToSend() {
if (!conn) return;
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData || !conn) {
return;
}
bool needsLayer = !sessionData->layerWasInited();
bool prependOnly = false;
mtpRequest pingRequest;
if (toSendPingId) {
@@ -1370,18 +1398,18 @@ void MTProtoConnectionPrivate::tryToSend() {
prependOnly = (getState() != MTProtoConnection::Connected);
DEBUG_LOG(("MTP Info: sending ping, ping_id: %1, prepend_only: %2").arg(ping.vping_id.v).arg(prependOnly ? "[TRUE]" : "[FALSE]"));
uint32 pingSize = ping.size() >> 2; // copy from MTProtoSession::send
uint32 pingSize = ping.innerLength() >> 2; // copy from MTProtoSession::send
pingRequest = mtpRequestData::prepare(pingSize);
ping.write(*pingRequest);
pingRequest->msDate = getms(); // > 0 - can send without container
pingRequest->msDate = getms(true); // > 0 - can send without container
pingRequest->requestId = 0; // dont add to haveSent / wereAcked maps
pingId = toSendPingId;
toSendPingId = 0;
} else {
int32 st = getState();
DEBUG_LOG(("MTP Info: trying to send after ping, state: %1").arg(st));
DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2").arg(dc).arg(st));
if (st != MTProtoConnection::Connected) {
return; // just do nothing, if is not connected yet
}
@@ -1391,10 +1419,10 @@ void MTProtoConnectionPrivate::tryToSend() {
if (!prependOnly && !ackRequestData.isEmpty()) {
MTPMsgsAck ack(MTP_msgs_ack(MTP_vector<MTPlong>(ackRequestData)));
ackRequest = mtpRequestData::prepare(ack.size() >> 2);
ackRequest = mtpRequestData::prepare(ack.innerLength() >> 2);
ack.write(*ackRequest);
ackRequest->msDate = getms(); // > 0 - can send without container
ackRequest->msDate = getms(true); // > 0 - can send without container
ackRequest->requestId = 0; // dont add to haveSent / wereAcked maps
ackRequestData.clear();
@@ -1402,10 +1430,10 @@ void MTProtoConnectionPrivate::tryToSend() {
if (!prependOnly && !resendRequestData.isEmpty()) {
MTPMsgResendReq resend(MTP_msg_resend_req(MTP_vector<MTPlong>(resendRequestData)));
resendRequest = mtpRequestData::prepare(resend.size() >> 2);
resendRequest = mtpRequestData::prepare(resend.innerLength() >> 2);
resend.write(*resendRequest);
resendRequest->msDate = getms(); // > 0 - can send without container
resendRequest->msDate = getms(true); // > 0 - can send without container
resendRequest->requestId = 0; // dont add to haveSent / wereAcked maps
resendRequestData.clear();
@@ -1426,14 +1454,22 @@ void MTProtoConnectionPrivate::tryToSend() {
if (!stateReq.isEmpty()) {
MTPMsgsStateReq req(MTP_msgs_state_req(MTP_vector<MTPlong>(stateReq)));
stateRequest = mtpRequestData::prepare(req.size() >> 2);
stateRequest = mtpRequestData::prepare(req.innerLength() >> 2);
req.write(*stateRequest);
stateRequest->msDate = getms(); // > 0 - can send without container
stateRequest->msDate = getms(true); // > 0 - can send without container
stateRequest->requestId = reqid();// add to haveSent / wereAcked maps, but don't add to requestMap
}
}
MTPInitConnection<mtpRequest> initWrapperImpl, *initWrapper = &initWrapperImpl;
int32 initSize = 0, initSizeInInts = 0;
if (needsLayer) {
initWrapperImpl = MTPInitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(ApiLang), mtpRequest());
initSizeInInts = (initWrapper->innerLength() >> 2) + 2;
initSize = initSizeInInts * sizeof(mtpPrime);
}
bool needAnyResponse = false;
mtpRequest toSendRequest;
{
@@ -1468,19 +1504,32 @@ void MTProtoConnectionPrivate::tryToSend() {
if (toSendRequest->requestId) {
if (mtpRequestData::needAck(toSendRequest)) {
toSendRequest->msDate = mtpRequestData::isStateRequest(toSendRequest) ? 0 : getms();
toSendRequest->msDate = mtpRequestData::isStateRequest(toSendRequest) ? 0 : getms(true);
QWriteLocker locker2(sessionData->haveSentMutex());
mtpRequestMap &haveSent(sessionData->haveSentMap());
haveSent.insert(msgId, toSendRequest);
if (needsLayer && !toSendRequest->needsLayer) needsLayer = false;
if (toSendRequest->after) {
int32 toSendSize = toSendRequest->at(7) >> 2;
int32 toSendSize = toSendRequest.innerLength() >> 2;
mtpRequest wrappedRequest(mtpRequestData::prepare(toSendSize, toSendSize + 3)); // cons + msg_id
wrappedRequest->resize(4);
memcpy(wrappedRequest->data(), toSendRequest->constData(), 4 * sizeof(mtpPrime));
_mtp_internal::wrapInvokeAfter(wrappedRequest, toSendRequest, haveSent);
toSendRequest = wrappedRequest;
}
if (needsLayer) {
int32 noWrapSize = (toSendRequest.innerLength() >> 2), toSendSize = noWrapSize + initSizeInInts;
mtpRequest wrappedRequest(mtpRequestData::prepare(toSendSize));
memcpy(wrappedRequest->data(), toSendRequest->constData(), 7 * sizeof(mtpPrime)); // all except length
wrappedRequest->push_back(mtpc_invokeWithLayer);
wrappedRequest->push_back(mtpCurrentLayer);
initWrapper->write(*wrappedRequest);
wrappedRequest->resize(wrappedRequest->size() + noWrapSize);
memcpy(wrappedRequest->data() + wrappedRequest->size() - noWrapSize, toSendRequest->constData() + 8, noWrapSize * sizeof(mtpPrime));
toSendRequest = wrappedRequest;
}
needAnyResponse = true;
} else {
@@ -1489,6 +1538,7 @@ void MTProtoConnectionPrivate::tryToSend() {
}
}
} else { // send in container
bool willNeedInit = false;
uint32 containerSize = 1 + 1, idsWrapSize = (toSendCount << 1); // cons + vector size, idsWrapSize - size of "request-like" wrap for msgId vector
if (pingRequest) containerSize += mtpRequestData::messageSize(pingRequest);
if (ackRequest) containerSize += mtpRequestData::messageSize(ackRequest);
@@ -1496,6 +1546,17 @@ void MTProtoConnectionPrivate::tryToSend() {
if (stateRequest) containerSize += mtpRequestData::messageSize(stateRequest);
for (mtpPreRequestMap::iterator i = toSend.begin(), e = toSend.end(); i != e; ++i) {
containerSize += mtpRequestData::messageSize(i.value());
if (needsLayer && i.value()->needsLayer) {
containerSize += initSizeInInts;
willNeedInit = true;
}
}
mtpBuffer initSerialized;
if (willNeedInit) {
initSerialized.reserve(initSizeInInts);
initSerialized.push_back(mtpc_invokeWithLayer);
initSerialized.push_back(mtpCurrentLayer);
initWrapper->write(initSerialized);
}
toSendRequest = mtpRequestData::prepare(containerSize, containerSize + 3 * toSend.size()); // prepare container + each in invoke after
toSendRequest->push_back(mtpc_msg_container);
@@ -1529,9 +1590,21 @@ void MTProtoConnectionPrivate::tryToSend() {
bool added = false;
if (req->requestId) {
if (mtpRequestData::needAck(req)) {
req->msDate = mtpRequestData::isStateRequest(req) ? 0 : getms();
req->msDate = mtpRequestData::isStateRequest(req) ? 0 : getms(true);
int32 reqNeedsLayer = (needsLayer && req->needsLayer) ? toSendRequest->size() : 0;
if (req->after) {
_mtp_internal::wrapInvokeAfter(toSendRequest, req, haveSent);
_mtp_internal::wrapInvokeAfter(toSendRequest, req, haveSent, reqNeedsLayer ? initSizeInInts : 0);
if (reqNeedsLayer) {
memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize);
*(toSendRequest->data() + reqNeedsLayer + 3) += initSize;
}
added = true;
} else if (reqNeedsLayer) {
toSendRequest->resize(reqNeedsLayer + initSizeInInts + mtpRequestData::messageSize(req));
memcpy(toSendRequest->data() + reqNeedsLayer, req->constData() + 4, 4 * sizeof(mtpPrime));
memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize);
memcpy(toSendRequest->data() + reqNeedsLayer + 4 + initSizeInInts, req->constData() + 8, req.innerLength());
*(toSendRequest->data() + reqNeedsLayer + 3) += initSize;
added = true;
}
haveSent.insert(msgId, req);
@@ -1567,6 +1640,9 @@ void MTProtoConnectionPrivate::tryToSend() {
}
void MTProtoConnectionPrivate::retryByTimer() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
if (retryTimeout < 3) {
++retryTimeout;
} else if (retryTimeout == 3) {
@@ -1629,6 +1705,9 @@ void MTProtoConnectionPrivate::socketStart(bool afterConfig) {
}
void MTProtoConnectionPrivate::restart(bool maybeBadKey) {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
DEBUG_LOG(("MTP Info: restarting MTProtoConnection, maybe bad key = %1").arg(logBool(maybeBadKey)));
connCheckTimer.stop();
@@ -1675,7 +1754,7 @@ void MTProtoConnectionPrivate::onSentSome(uint64 size) {
}
connCheckTimer.start(remain);
}
if (!firstSentAt) firstSentAt = getms();
if (!firstSentAt) firstSentAt = getms(true);
}
void MTProtoConnectionPrivate::onReceivedSome() {
@@ -1686,17 +1765,17 @@ void MTProtoConnectionPrivate::onReceivedSome() {
oldConnectionTimer.start(MTPConnectionOldTimeout);
connCheckTimer.stop();
if (firstSentAt > 0) {
int32 ms = getms() - firstSentAt;
int32 ms = getms(true) - firstSentAt;
DEBUG_LOG(("MTP Info: response in %1ms, receiveDelay: %2ms").arg(ms).arg(receiveDelay));
if (ms > 0 && ms * 2 < int32(receiveDelay)) receiveDelay = qMax(ms * 2, int32(MinReceiveDelay));
if (ms > 0 && ms * 2 < int32(receiveDelay)) receiveDelay = qMax(ms * 2, int32(MTPMinReceiveDelay));
firstSentAt = -1;
}
}
void MTProtoConnectionPrivate::onOldConnection() {
oldConnection = true;
receiveDelay = MinReceiveDelay;
receiveDelay = MTPMinReceiveDelay;
DEBUG_LOG(("This connection marked as old! delay now %1ms").arg(receiveDelay));
}
@@ -1743,6 +1822,9 @@ void MTProtoConnectionPrivate::doFinish() {
}
void MTProtoConnectionPrivate::handleReceived() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
onReceivedSome();
ReadLockerAttempt lock(sessionData->keyMutex());
@@ -1872,7 +1954,7 @@ void MTProtoConnectionPrivate::handleReceived() {
uint32 toAckSize = ackRequestData.size();
if (toAckSize) {
DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(logVectorLong(ackRequestData)));
sessionData->owner()->sendAnything(MTPAckSendWaiting);
emit sendAnythingAsync(MTPAckSendWaiting);
}
bool emitSignal = false;
@@ -1900,12 +1982,12 @@ void MTProtoConnectionPrivate::handleReceived() {
if (!wasConnected) {
if (getState() == MTProtoConnection::Connected) {
emit sessionData->owner()->needToSendAsync();
emit needToSendAsync();
}
}
}
if (conn->needHttpWait()) {
sessionData->owner()->send(MTPHttpWait(MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000))));
emit sendHttpWait();
}
}
@@ -2320,6 +2402,10 @@ int32 MTProtoConnectionPrivate::handleOneReceived(const mtpPrime *from, const mt
response.resize(end - from);
memcpy(response.data(), from, (end - from) * sizeof(mtpPrime));
}
if (!sessionData->layerWasInited()) {
sessionData->setLayerWasInited(true);
sessionData->owner()->notifyLayerInited(true);
}
mtpRequestId requestId = wasSent(reqMsgId.v);
if (requestId && requestId != mtpRequestId(0xFFFFFFFF)) {
@@ -2634,6 +2720,9 @@ mtpRequestId MTProtoConnectionPrivate::resend(mtpMsgId msgId, uint64 msCanWait,
}
void MTProtoConnectionPrivate::onConnected() {
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()));
@@ -2673,7 +2762,8 @@ void MTProtoConnectionPrivate::onConnected() {
}
bool MTProtoConnectionPrivate::updateAuthKey() {
if (!conn) return false;
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData || !conn) return false;
DEBUG_LOG(("AuthKey Info: MTProtoConnection updating key from MTProtoSession, dc %1").arg(dc));
uint64 newKeyId = 0;
@@ -2774,7 +2864,7 @@ void MTProtoConnectionPrivate::pqAnswered() {
string &dhEncString(req_DH_params.vencrypted_data._string().v);
uint32 p_q_inner_size = p_q_inner.size(), encSize = (p_q_inner_size >> 2) + 6;
uint32 p_q_inner_size = p_q_inner.innerLength(), encSize = (p_q_inner_size >> 2) + 6;
if (encSize >= 65) {
mtpBuffer tmp;
tmp.reserve(encSize);
@@ -2841,7 +2931,7 @@ void MTProtoConnectionPrivate::dhParamsAnswered() {
return restart();
}
uint32 nlen = authKeyData->new_nonce.size(), slen = authKeyData->server_nonce.size();
uint32 nlen = authKeyData->new_nonce.innerLength(), slen = authKeyData->server_nonce.innerLength();
uchar tmp_aes[1024], sha1ns[20], sha1sn[20], sha1nn[20];
memcpy(tmp_aes, &authKeyData->new_nonce, nlen);
memcpy(tmp_aes + nlen, &authKeyData->server_nonce, slen);
@@ -2966,7 +3056,7 @@ void MTProtoConnectionPrivate::dhClientParamsSend() {
string &sdhEncString(req_client_DH_params.vencrypted_data._string().v);
uint32 client_dh_inner_size = client_dh_inner.size(), encSize = (client_dh_inner_size >> 2) + 5, encFullSize = encSize;
uint32 client_dh_inner_size = client_dh_inner.innerLength(), encSize = (client_dh_inner_size >> 2) + 5, encFullSize = encSize;
if (encSize & 0x03) {
encFullSize += 4 - (encSize & 0x03);
}
@@ -2993,6 +3083,9 @@ void MTProtoConnectionPrivate::dhClientParamsSend() {
}
void MTProtoConnectionPrivate::dhClientParamsAnswered() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
disconnect(conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered()));
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer.."));
@@ -3031,7 +3124,7 @@ void MTProtoConnectionPrivate::dhClientParamsAnswered() {
DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2, auth key: %3").arg(authKey->keyId()).arg(serverSalt).arg(mb(authKeyData->auth_key, 256).str()));
sessionData->owner()->keyCreated(authKey); // slot will call authKeyCreated()
sessionData->owner()->notifyKeyCreated(authKey); // slot will call authKeyCreated()
sessionData->clear();
unlockKey();
} return;
@@ -3100,7 +3193,7 @@ void MTProtoConnectionPrivate::authKeyCreated() {
toSendPingId = MTP::nonce<uint64>(); // get server_salt
emit sessionData->owner()->needToSendAsync();
emit needToSendAsync();
// disconnect(&pinger, SIGNAL(timeout()), 0, 0);
// connect(&pinger, SIGNAL(timeout()), this, SLOT(sendPing()));
@@ -3130,7 +3223,7 @@ void MTProtoConnectionPrivate::sendPing() {
}
void MTProtoConnectionPrivate::onError(bool mayBeBadKey) {
MTP_LOG(dc, ("Restarting after error.."));
MTP_LOG(dc, ("Restarting after error, maybe bad key: %1..").arg(logBool(mayBeBadKey)));
return restart(mayBeBadKey);
}
@@ -3141,7 +3234,7 @@ template <typename TRequest>
void MTProtoConnectionPrivate::sendRequestNotSecure(const TRequest &request) {
try {
mtpBuffer buffer;
uint32 requestSize = request.size() >> 2;
uint32 requestSize = request.innerLength() >> 2;
buffer.resize(0);
buffer.reserve(8 + requestSize);
@@ -3248,6 +3341,7 @@ 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);
if (needAnyResponse) {
@@ -3287,6 +3381,9 @@ void MTProtoConnectionPrivate::lockKey() {
}
void MTProtoConnectionPrivate::unlockKey() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
if (myKeyLock) {
myKeyLock = false;
sessionData->keyMutex()->unlock();
@@ -3297,6 +3394,10 @@ MTProtoConnectionPrivate::~MTProtoConnectionPrivate() {
doDisconnect();
}
MTProtoConnection::~MTProtoConnection() {
stopped();
void MTProtoConnectionPrivate::stop() {
QWriteLocker lockFinished(&sessionDataMutex);
sessionData = 0;
}
MTProtoConnection::~MTProtoConnection() {
}

View File

@@ -71,7 +71,6 @@ public:
MTProtoConnection();
int32 start(MTPSessionData *data, int32 dc = 0); // return dc
void restart();
void stop();
void stopped();
~MTProtoConnection();
@@ -106,6 +105,13 @@ class MTPabstractConnection : public QObject {
public:
MTPabstractConnection() : _sentEncrypted(false) {
}
void setSentEncrypted() {
_sentEncrypted = true;
}
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;
@@ -135,6 +141,7 @@ signals:
protected:
BuffersQueue receivedQueue; // list of received packets, not processed yet
bool _sentEncrypted;
};
@@ -189,6 +196,8 @@ public slots:
void onSocketDisconnected();
void onHttpStart();
void onTcpTimeoutTimer();
protected:
void socketPacket(mtpPrime *packet, uint32 packetSize);
@@ -215,6 +224,10 @@ private:
typedef QSet<QNetworkReply*> Requests;
Requests requests;
QString _addr;
int32 _port, _tcpTimeout;
QTimer tcpTimeoutTimer;
};
class MTPtcpConnection : public MTPabstractTcpConnection {
@@ -282,6 +295,8 @@ public:
MTProtoConnectionPrivate(QThread *thread, MTProtoConnection *owner, MTPSessionData *data, uint32 dc);
~MTProtoConnectionPrivate();
void stop();
int32 getDC() const;
int32 getState() const;
@@ -293,6 +308,10 @@ signals:
void needToRestart();
void stateChanged(qint32 newState);
void sessionResetDone();
void needToSendAsync();
void sendAnythingAsync(quint64);
void sendHttpWait();
public slots:
@@ -357,14 +376,14 @@ private:
MTProtoConnection *_owner;
MTPabstractConnection *conn;
QTimer retryTimer; // exp retry timer
SingleTimer retryTimer; // exp retry timer
uint32 retryTimeout;
quint64 retryWillFinish;
QTimer oldConnectionTimer;
SingleTimer oldConnectionTimer;
bool oldConnection;
QTimer connCheckTimer;
SingleTimer connCheckTimer;
uint32 receiveDelay;
int64 firstSentAt;
@@ -390,6 +409,7 @@ private:
bool restarted;
uint64 keyId;
QReadWriteLock sessionDataMutex;
MTPSessionData *sessionData;
bool myKeyLock;
void lockKey();

View File

@@ -172,12 +172,20 @@ void mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpP
} break;
default: {
for (uint32 i = 1; i < mtpLayerMax; ++i) {
for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) {
if (cons == mtpLayers[i]) {
to.add("[LAYER").add(mtpWrapNumber(i + 1)).add("] "); mtpTextSerializeType(to, from, end, 0, level);
return;
}
}
if (cons == mtpc_invokeWithLayer) {
if (from >= end) {
throw Exception("from >= end in invokeWithLayer");
}
int32 layer = *(from++);
to.add("[LAYER").add(mtpWrapNumber(layer)).add("] "); mtpTextSerializeType(to, from, end, 0, level);
return;
}
throw Exception(QString("unknown cons 0x%1").arg(cons, 0, 16));
} break;
}

View File

@@ -62,7 +62,7 @@ public:
explicit mtpRequest(mtpRequestData *ptr) : QSharedPointer<mtpRequestData>(ptr) {
}
uint32 size() const;
uint32 innerLength() const;
void write(mtpBuffer &to) const;
typedef void ResponseType; // don't know real response type =(
@@ -74,10 +74,12 @@ public:
// in toSend: = 0 - must send in container, > 0 - can send without container
// in haveSent: = 0 - container with msgIds, > 0 - when was sent
uint64 msDate;
mtpRequestId requestId;
mtpRequest after;
bool needsLayer;
mtpRequestData(bool/* sure*/) : msDate(0), requestId(0) {
mtpRequestData(bool/* sure*/) : msDate(0), requestId(0), needsLayer(false) {
}
static mtpRequest prepare(uint32 requestSize, uint32 maxSize = 0) {
@@ -92,7 +94,7 @@ public:
static void padding(mtpRequest &request) {
if (request->size() < 9) return;
uint32 requestSize = ((*request)[7] >> 2), padding = _padding(requestSize), fullSize = 8 + requestSize + padding; // 2: salt, 2: session_id, 2: msg_id, 1: seq_no, 1: message_length
uint32 requestSize = (request.innerLength() >> 2), padding = _padding(requestSize), fullSize = 8 + requestSize + padding; // 2: salt, 2: session_id, 2: msg_id, 1: seq_no, 1: message_length
if (uint32(request->size()) != fullSize) {
request->resize(fullSize);
if (padding) {
@@ -103,7 +105,7 @@ public:
static uint32 messageSize(const mtpRequest &request) {
if (request->size() < 9) return 0;
return 4 + ((*request)[7] >> 2); // 2: msg_id, 1: seq_no, q: message_length
return 4 + (request.innerLength() >> 2); // 2: msg_id, 1: seq_no, q: message_length
}
static bool isSentContainer(const mtpRequest &request); // "request-like" wrap for msgIds vector
@@ -119,7 +121,7 @@ private:
};
inline uint32 mtpRequest::size() const { // for template MTP requests and MTPBoxed instanciation
inline uint32 mtpRequest::innerLength() const { // for template MTP requests and MTPBoxed instanciation
mtpRequestData *value = data();
if (!value || value->size() < 9) return 0;
return value->at(7);
@@ -128,7 +130,7 @@ inline uint32 mtpRequest::size() const { // for template MTP requests and MTPBox
inline void mtpRequest::write(mtpBuffer &to) const {
mtpRequestData *value = data();
if (!value || value->size() < 9) return;
uint32 was = to.size(), s = size() / sizeof(mtpPrime);
uint32 was = to.size(), s = innerLength() / sizeof(mtpPrime);
to.resize(was + s);
memcpy(to.data() + was, value->constData() + 8, s * sizeof(mtpPrime));
}
@@ -335,6 +337,8 @@ enum {
mtpc_invokeWithLayer17 = 0x50858a19,
mtpc_invokeWithLayer18 = 0x1c900537,
mtpc_invokeWithLayer = 0xda9b0d0d, // after 18 layer
// manually parsed
mtpc_rpc_result = 0xf35c6d01,
mtpc_msg_container = 0x73f1f8dc,
@@ -362,7 +366,8 @@ static const mtpTypeId mtpLayers[] = {
mtpc_invokeWithLayer16,
mtpc_invokeWithLayer17,
mtpc_invokeWithLayer18,
}, mtpLayerMax = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
}, mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
static const mtpPrime mtpCurrentLayer = 19;
template <typename bareT>
class MTPBoxed : public bareT {
@@ -386,8 +391,8 @@ public:
return *this;
}
uint32 size() const {
return sizeof(mtpTypeId) + bareT::size();
uint32 innerLength() const {
return sizeof(mtpTypeId) + bareT::innerLength();
}
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) {
if (from + 1 > end) throw mtpErrorInsufficient();
@@ -414,7 +419,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(int32);
}
mtpTypeId type() const {
@@ -457,7 +462,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(uint64);
}
mtpTypeId type() const {
@@ -503,7 +508,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(uint64) + sizeof(uint64);
}
mtpTypeId type() const {
@@ -552,8 +557,8 @@ public:
read(from, end, cons);
}
uint32 size() const {
return l.size() + h.size();
uint32 innerLength() const {
return l.innerLength() + h.innerLength();
}
mtpTypeId type() const {
return mtpc_int256;
@@ -596,7 +601,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return sizeof(float64);
}
mtpTypeId type() const {
@@ -666,7 +671,7 @@ public:
return *(const MTPDstring*)data;
}
uint32 size() const {
uint32 innerLength() const {
uint32 l = c_string().v.length();
if (l < 254) {
l += 1;
@@ -770,7 +775,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return 0;
}
mtpTypeId type() const {
@@ -858,10 +863,10 @@ public:
return *(const MTPDvector<T>*)data;
}
uint32 size() const {
uint32 innerLength() const {
uint32 result(sizeof(uint32));
for (typename VType::const_iterator i = c_vector().v.cbegin(), e = c_vector().v.cend(); i != e; ++i) {
result += i->size();
result += i->innerLength();
}
return result;
}
@@ -961,8 +966,8 @@ public:
return *(const MTPDerror*)data;
}
uint32 size() const {
return c_error().vcode.size() + c_error().vtext.size();
uint32 innerLength() const {
return c_error().vcode.innerLength() + c_error().vtext.innerLength();
}
mtpTypeId type() const {
return mtpc_error;
@@ -999,7 +1004,7 @@ public:
read(from, end, cons);
}
uint32 size() const {
uint32 innerLength() const {
return 0;
}
mtpTypeId type() const {

View File

@@ -19,6 +19,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "mtpDC.h"
#include "mtp.h"
#include "localstorage.h"
namespace {
MTProtoDCMap gDCs;
@@ -65,7 +67,7 @@ namespace {
QByteArray data, decrypted;
stream >> data;
if (!MTP::localKey().created()) {
if (!Local::oldKey().created()) {
LOG(("MTP Error: reading encrypted keys without local key!"));
continue;
}
@@ -77,7 +79,7 @@ namespace {
uint32 fullDataLen = data.size() - 16;
decrypted.resize(fullDataLen);
const char *dataKey = data.constData(), *encrypted = data.constData() + 16;
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &MTP::localKey(), dataKey);
aesDecryptLocal(encrypted, decrypted.data(), fullDataLen, &Local::oldKey(), dataKey);
uchar sha1Buffer[20];
if (memcmp(hashSha1(decrypted.constData(), decrypted.size(), sha1Buffer), dataKey, 16)) {
LOG(("MTP Error: bad decrypt key, data from user-config not decrypted"));
@@ -271,7 +273,7 @@ namespace {
}
QByteArray encrypted(16 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &MTP::localKey(), encrypted.constData());
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 16, fullSize, &Local::oldKey(), encrypted.constData());
DEBUG_LOG(("MTP Info: keys file opened for writing %1 keys").arg(keysToWrite.size()));
QDataStream keysStream(&keysFile);
@@ -350,7 +352,7 @@ void mtpSetDC(int32 dc) {
}
}
MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false), _connectionInitSent(false) {
MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false) {
connect(this, SIGNAL(authKeyCreated()), this, SLOT(authKeyWrite()), Qt::QueuedConnection);
QMutexLocker lock(&_keysMapForWriteMutex);
@@ -371,6 +373,7 @@ void MTProtoDC::authKeyWrite() {
void MTProtoDC::setKey(const mtpAuthKeyPtr &key) {
DEBUG_LOG(("AuthKey Info: MTProtoDC::setKey(%1), emitting authKeyCreated, dc %2").arg(key ? key->keyId() : 0).arg(_id));
_key = key;
_connectionInited = false;
emit authKeyCreated();
QMutexLocker lock(&_keysMapForWriteMutex);
@@ -443,7 +446,6 @@ void mtpUpdateDcOptions(const QVector<MTPDcOption> &options) {
MTProtoConfigLoader::MTProtoConfigLoader() : _enumCurrent(0), _enumRequest(0) {
connect(&_enumDCTimer, SIGNAL(timeout()), this, SLOT(enumDC()));
_enumDCTimer.setSingleShot(true);
}
void MTProtoConfigLoader::load() {
@@ -458,7 +460,7 @@ void MTProtoConfigLoader::load() {
void MTProtoConfigLoader::done() {
_enumDCTimer.stop();
if (_enumRequest) MTP::cancel(_enumRequest);
if (_enumCurrent) MTP::killSession(_enumCurrent);
if (_enumCurrent) MTP::killSession(MTP::cfg + _enumCurrent);
emit loaded();
}

View File

@@ -29,13 +29,6 @@ public:
void setKey(const mtpAuthKeyPtr &key);
void destroyKey();
bool needConnectionInit() {
QMutexLocker lock(&initLock);
if (_connectionInited || _connectionInitSent) return false;
_connectionInitSent = true;
return true;
}
bool connectionInited() const {
QMutexLocker lock(&initLock);
bool res = _connectionInited;
@@ -49,6 +42,7 @@ public:
signals:
void authKeyCreated();
void layerWasInited(bool was);
private slots:
@@ -61,7 +55,6 @@ private:
int32 _id;
mtpAuthKeyPtr _key;
bool _connectionInited;
bool _connectionInitSent;
};
typedef QSharedPointer<MTProtoDC> MTProtoDCPtr;
@@ -97,7 +90,7 @@ signals:
private:
QTimer _enumDCTimer;
SingleTimer _enumDCTimer;
int32 _enumCurrent;
mtpRequestId _enumRequest;

View File

@@ -20,6 +20,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#include "window.h"
#include "application.h"
#include "localstorage.h"
namespace {
int32 _priority = 1;
@@ -44,9 +45,9 @@ namespace {
}
mtpFileLoader::mtpFileLoader(int32 dc, const int64 &volume, int32 local, const int64 &secret, int32 size) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(0), volume(volume), local(local), secret(secret),
id(0), access(0), size(size), type(MTP_storage_fileUnknown()) {
id(0), access(0), fileIsOpen(false), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(dc);
if (i == queues.cend()) {
i = queues.insert(dc, mtpFileLoaderQueue());
@@ -55,9 +56,9 @@ id(0), access(0), size(size), type(MTP_storage_fileUnknown()) {
}
mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(locType),
id(id), access(access), file(to), duplicateInData(false), size(size), type(MTP_storage_fileUnknown()) {
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(false), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc);
if (i == queues.cend()) {
i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue());
@@ -66,9 +67,9 @@ id(id), access(access), file(to), duplicateInData(false), size(size), type(MTP_s
}
mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, mtpTypeId locType, const QString &to, int32 size, bool todata) : prev(0), next(0),
priority(0), inQueue(false), complete(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
priority(0), inQueue(false), complete(false), triedLocal(false), skippedBytes(0), nextRequestOffset(0), lastComplete(false),
dc(dc), locationType(locType),
id(id), access(access), file(to), duplicateInData(todata), size(size), type(MTP_storage_fileUnknown()) {
id(id), access(access), file(to), fname(to), fileIsOpen(false), duplicateInData(todata), size(size), type(mtpc_storage_fileUnknown) {
LoaderQueues::iterator i = queues.find(MTP::dld[0] + dc);
if (i == queues.cend()) {
i = queues.insert(MTP::dld[0] + dc, mtpFileLoaderQueue());
@@ -77,7 +78,7 @@ id(id), access(access), file(to), duplicateInData(todata), size(size), type(MTP_
}
QString mtpFileLoader::fileName() const {
return file.fileName();
return fname;
}
bool mtpFileLoader::done() const {
@@ -85,7 +86,7 @@ bool mtpFileLoader::done() const {
}
mtpTypeId mtpFileLoader::fileType() const {
return type.type();
return type;
}
const QByteArray &mtpFileLoader::bytes() const {
@@ -99,16 +100,16 @@ float64 mtpFileLoader::currentProgress() const {
}
int32 mtpFileLoader::currentOffset(bool includeSkipped) const {
return (file.isOpen() ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes);
return (fileIsOpen ? file.size() : data.size()) - (includeSkipped ? 0 : skippedBytes);
}
int32 mtpFileLoader::fullSize() const {
return size;
}
void mtpFileLoader::setFileName(const QString &fname) {
if (duplicateInData && file.fileName().isEmpty()) {
file.setFileName(fname);
void mtpFileLoader::setFileName(const QString &fileName) {
if (duplicateInData && fname.isEmpty()) {
file.setFileName(fname = fileName);
}
}
@@ -130,15 +131,16 @@ void mtpFileLoader::loadNext() {
void mtpFileLoader::finishFail() {
bool started = currentOffset(true) > 0;
cancelRequests();
type = MTP_storage_fileUnknown();
type = mtpc_storage_fileUnknown;
complete = true;
if (file.isOpen()) {
if (fileIsOpen) {
file.close();
fileIsOpen = false;
file.remove();
}
data = QByteArray();
emit failed(this, started);
file.setFileName(QString());
file.setFileName(fname = QString());
loadNext();
}
@@ -173,8 +175,7 @@ bool mtpFileLoader::loadPart() {
App::app()->killDownloadSessionsStop(dc);
}
MTPupload_GetFile request(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit)));
mtpRequestId reqId = MTP::send(request, rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + dc, 50);
mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld[dcIndex] + dc, 50);
++queue->queries;
dr.v[dcIndex] += limit;
@@ -186,7 +187,7 @@ bool mtpFileLoader::loadPart() {
void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req) {
Requests::iterator i = requests.find(req);
if (i == requests.cend()) return;
if (i == requests.cend()) return loadNext();
int32 limit = locationType ? DocumentDownloadPartSize : DownloadPartSize;
int32 dcIndex = i.value();
@@ -198,7 +199,7 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
const MTPDupload_file &d(result.c_upload_file());
const string &bytes(d.vbytes.c_string().v);
if (bytes.size()) {
if (file.isOpen()) {
if (fileIsOpen) {
int64 fsize = file.size();
if (offset < fsize) {
skippedBytes -= bytes.size();
@@ -230,27 +231,32 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
lastComplete = true;
}
if (requests.isEmpty() && (lastComplete || (size && nextRequestOffset >= size))) {
if (duplicateInData && !file.fileName().isEmpty()) {
if (!file.open(QIODevice::WriteOnly)) {
if (!fname.isEmpty() && duplicateInData) {
if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
if (file.write(data) != qint64(data.size())) {
return finishFail();
}
}
type = d.vtype;
type = d.vtype.type();
complete = true;
if (file.isOpen()) {
if (fileIsOpen) {
file.close();
fileIsOpen = false;
psPostprocessFile(QFileInfo(file).absoluteFilePath());
}
removeFromQueue();
App::wnd()->update();
App::wnd()->notifyUpdateAllPhotos();
if (!queue->queries && dcIndex) {
App::app()->killDownloadSessionsStart(dc);
}
if (!locationType && triedLocal && (fname.isEmpty() || duplicateInData)) {
Local::writeImage(storageKey(dc, volume, local), StorageImageSaved(type, data));
}
}
emit progress(this);
loadNext();
@@ -285,11 +291,38 @@ void mtpFileLoader::pause() {
void mtpFileLoader::start(bool loadFirst, bool prior) {
if (complete) return;
if (!locationType && !triedLocal) {
triedLocal = true;
StorageImageSaved cached = Local::readImage(storageKey(dc, volume, local));
if (cached.type != mtpc_storage_fileUnknown) {
data = cached.data;
if (!fname.isEmpty() && duplicateInData) {
if (!fileIsOpen) fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
if (file.write(data) != qint64(data.size())) {
return finishFail();
}
}
type = cached.type;
complete = true;
if (fileIsOpen) {
file.close();
fileIsOpen = false;
psPostprocessFile(QFileInfo(file).absoluteFilePath());
}
App::wnd()->update();
App::wnd()->notifyUpdateAllPhotos();
emit progress(this);
return loadNext();
}
}
if (!file.fileName().isEmpty() && !duplicateInData) {
if (!file.open(QIODevice::WriteOnly)) {
finishFail();
return;
if (!fname.isEmpty() && !duplicateInData && !fileIsOpen) {
fileIsOpen = file.open(QIODevice::WriteOnly);
if (!fileIsOpen) {
return finishFail();
}
}
@@ -383,10 +416,11 @@ void mtpFileLoader::start(bool loadFirst, bool prior) {
void mtpFileLoader::cancel() {
cancelRequests();
type = MTP_storage_fileUnknown();
type = mtpc_storage_fileUnknown;
complete = true;
if (file.isOpen()) {
if (fileIsOpen) {
file.close();
fileIsOpen = false;
file.remove();
}
data = QByteArray();

View File

@@ -60,7 +60,7 @@ signals:
private:
mtpFileLoaderQueue *queue;
bool inQueue, complete;
bool inQueue, complete, triedLocal;
void cancelRequests();
@@ -89,11 +89,13 @@ private:
uint64 id; // for other locations
uint64 access;
QFile file;
QString fname;
bool fileIsOpen;
bool duplicateInData;
QByteArray data;
int32 size;
MTPstorage_FileType type;
mtpTypeId type;
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -83,10 +83,6 @@ void MTProtoSession::start(int32 dcenter, uint32 connects) {
timeouter.start(1000);
connect(&sender, SIGNAL(timeout()), this, SIGNAL(needToSend()));
connect(this, SIGNAL(startSendTimer(int)), &sender, SLOT(start(int)));
connect(this, SIGNAL(stopSendTimer()), &sender, SLOT(stop()));
connect(this, SIGNAL(needToSendAsync()), this, SIGNAL(needToSend()));
sender.setSingleShot(true);
MTProtoDCMap &dcs(mtpDCMap());
@@ -114,27 +110,28 @@ void MTProtoSession::start(int32 dcenter, uint32 connects) {
ReadLockerAttempt lock(keyMutex());
data.setKey(lock ? dc->getKey() : mtpAuthKeyPtr(0));
if (lock && dc->connectionInited()) {
data.setLayerWasInited(true);
}
connect(dc.data(), SIGNAL(authKeyCreated()), this, SLOT(authKeyCreatedForDC()));
connect(dc.data(), SIGNAL(layerWasInited(bool)), this, SLOT(layerWasInitedForDC(bool)));
}
}
}
void MTProtoSession::restart() {
for (MTProtoConnections::const_iterator i = connections.cbegin(), e = connections.cend(); i != e; ++i) {
(*i)->restart();
}
emit needToRestart();
}
void MTProtoSession::stop() {
while (connections.size()) {
while (!connections.isEmpty()) {
connections.back()->stop();
connections.pop_back();
}
}
void MTProtoSession::sendAnything(uint64 msCanWait) {
uint64 ms = getms();
void MTProtoSession::sendAnything(quint64 msCanWait) {
uint64 ms = getms(true);
if (msSendCall) {
if (ms > msSendCall + msWait) {
msWait = 0;
@@ -148,16 +145,21 @@ void MTProtoSession::sendAnything(uint64 msCanWait) {
msWait = msCanWait;
}
if (msWait) {
DEBUG_LOG(("MTP Info: dc %1 can wait for %2ms from current %3").arg(dcId).arg(msWait).arg(msSendCall));
msSendCall = ms;
emit startSendTimer(msWait);
DEBUG_LOG(("MTP Info: can wait for %1ms from current %2").arg(msWait).arg(msSendCall));
sender.start(msWait);
} else {
emit stopSendTimer();
DEBUG_LOG(("MTP Info: dc %1 stopped send timer, can wait for %2ms from current %3").arg(dcId).arg(msWait).arg(msSendCall));
sender.stop();
msSendCall = 0;
emit needToSendAsync();
emit needToSend();
}
}
void MTProtoSession::sendHttpWait() {
send(MTPHttpWait(MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000))), RPCResponseHandler(), 50);
}
void MTProtoSession::checkRequestsByTimer() {
QVector<mtpMsgId> resendingIds;
QVector<mtpMsgId> removingIds; // remove very old (10 minutes) containers and resend requests
@@ -167,7 +169,7 @@ void MTProtoSession::checkRequestsByTimer() {
QReadLocker locker(data.haveSentMutex());
mtpRequestMap &haveSent(data.haveSentMap());
uint32 haveSentCount(haveSent.size());
uint64 ms = getms();
uint64 ms = getms(true);
for (mtpRequestMap::iterator i = haveSent.begin(), e = haveSent.end(); i != e; ++i) {
mtpRequest &req(i.value());
if (req->msDate > 0) {
@@ -334,7 +336,7 @@ mtpRequestId MTProtoSession::resend(mtpMsgId msgId, uint64 msCanWait, bool force
}
return 0xFFFFFFFF;
} else if (!mtpRequestData::isStateRequest(request)) {
request->msDate = forceContainer ? 0 : getms();
request->msDate = forceContainer ? 0 : getms(true);
sendPrepared(request, msCanWait, false);
{
QWriteLocker locker(data.toResendMutex());
@@ -377,21 +379,9 @@ void MTProtoSession::sendPrepared(const mtpRequest &request, uint64 msCanWait, b
sendAnything(msCanWait);
}
void MTProtoSession::sendPreparedWithInit(const mtpRequest &request, uint64 msCanWait) { // returns true, if emit of needToSend() is needed
if (request->size() > 8 && request->at(8) == mtpc_initConnection) {
sendPrepared(request, msCanWait, false);
return;
}
{
MTPInitConnection<mtpRequest> requestWrap(MTPinitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(ApiLang), request));
uint32 requestSize = requestWrap.size() >> 2;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize));
requestWrap.write(*reqSerialized);
request->resize(reqSerialized->size());
memcpy(request->data(), reqSerialized->constData(), reqSerialized->size());
}
request->msDate = getms(); // > 0 - can send without container
sendPrepared(request, msCanWait);
void MTProtoSession::sendPreparedWithInit(const mtpRequest &request, uint64 msCanWait) {
request->needsLayer = true;
sendPrepared(request, msCanWait, false);
}
QReadWriteLock *MTProtoSession::keyMutex() const {
@@ -404,11 +394,22 @@ void MTProtoSession::authKeyCreatedForDC() {
emit authKeyCreated();
}
void MTProtoSession::keyCreated(const mtpAuthKeyPtr &key) {
void MTProtoSession::notifyKeyCreated(const mtpAuthKeyPtr &key) {
DEBUG_LOG(("AuthKey Info: MTProtoSession::keyCreated(), setting, dc %1").arg(dcId));
dc->setKey(key);
}
void MTProtoSession::layerWasInitedForDC(bool wasInited) {
DEBUG_LOG(("MTP Info: MTProtoSession::layerWasInitedForDC slot, dc %1").arg(dcId));
data.setLayerWasInited(wasInited);
}
void MTProtoSession::notifyLayerInited(bool wasInited) {
DEBUG_LOG(("MTP Info: emitting MTProtoDC::layerWasInited(%1), dc %2").arg(logBool(wasInited)).arg(dcId));
dc->setConnectionInited(wasInited);
emit dc->layerWasInited(wasInited);
}
void MTProtoSession::destroyKey() {
if (!dc) return;

View File

@@ -28,8 +28,8 @@ public:
MTPSessionData(MTProtoSession *creator)
: _session(0), _salt(0)
, _messagesSent(0), fakeRequestId(-2000000000)
, _owner(creator), keyChecked(false) {
, _messagesSent(0), _fakeRequestId(-2000000000)
, _owner(creator), _keyChecked(false), _layerInited(false) {
}
void setSession(uint64 session) {
@@ -45,6 +45,14 @@ public:
QReadLocker locker(&lock);
return _session;
}
bool layerWasInited() const {
QReadLocker locker(&lock);
return _layerInited;
}
void setLayerWasInited(bool was) {
QWriteLocker locker(&lock);
_layerInited = was;
}
void setSalt(uint64 salt) {
QWriteLocker locker(&lock);
@@ -56,26 +64,31 @@ public:
}
const mtpAuthKeyPtr &getKey() const {
return authKey;
return _authKey;
}
void setKey(const mtpAuthKeyPtr &key) {
if (authKey != key) {
if (_authKey != key) {
uint64 session;
memsetrnd(session);
authKey = key;
_authKey = key;
DEBUG_LOG(("MTP Info: new auth key set in SessionData, id %1, setting random server_session %2").arg(key ? key->keyId() : 0).arg(session));
setSession(session);
QWriteLocker locker(&lock);
if (_session != session) {
_session = session;
_messagesSent = 0;
}
_layerInited = false;
}
}
bool isCheckedKey() const {
QReadLocker locker(&lock);
return keyChecked;
return _keyChecked;
}
void setCheckedKey(bool checked) {
QWriteLocker locker(&lock);
keyChecked = checked;
_keyChecked = checked;
}
QReadWriteLock *keyMutex() const;
@@ -147,11 +160,11 @@ public:
mtpRequestId nextFakeRequestId() { // must be locked by haveReceivedMutex()
if (haveReceived.isEmpty() || haveReceived.cbegin().key() > 0) {
fakeRequestId = -2000000000;
_fakeRequestId = -2000000000;
} else {
++fakeRequestId;
++_fakeRequestId;
}
return fakeRequestId;
return _fakeRequestId;
}
MTProtoSession *owner() {
@@ -174,12 +187,12 @@ private:
uint64 _session, _salt;
uint32 _messagesSent;
mtpRequestId fakeRequestId;
mtpRequestId _fakeRequestId;
MTProtoSession *_owner;
mtpAuthKeyPtr authKey;
bool keyChecked;
mtpAuthKeyPtr _authKey;
bool _keyChecked, _layerInited;
mtpPreRequestMap toSend; // map of request_id -> request, that is waiting to be sent
mtpRequestMap haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers
@@ -216,12 +229,12 @@ public:
~MTProtoSession();
QReadWriteLock *keyMutex() const;
void keyCreated(const mtpAuthKeyPtr &key);
void notifyKeyCreated(const mtpAuthKeyPtr &key);
void destroyKey();
void notifyLayerInited(bool wasInited);
template <typename TRequest>
mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, uint32 layer = 0, bool toMainDC = false, mtpRequestId after = 0); // send mtp request
void sendAnything(uint64 msCanWait);
mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, bool needsLayer = false, bool toMainDC = false, mtpRequestId after = 0); // send mtp request
void cancel(mtpRequestId requestId, mtpMsgId msgId);
int32 requestState(mtpRequestId requestId) const;
@@ -237,27 +250,25 @@ public:
signals:
void authKeyCreated();
void needToSend();
void needToSendAsync(); // emit this signal, to emit needToSend() in MTProtoSession thread
void startSendTimer(int msec); // manipulating timer from all threads
void stopSendTimer();
void needToRestart();
public slots:
void authKeyCreatedForDC();
void layerWasInitedForDC(bool wasInited);
void tryToReceive();
void checkRequestsByTimer();
void onConnectionStateChange(qint32 newState);
void onResetDone();
void sendAnything(quint64 msCanWait);
void sendHttpWait();
private:
template <typename TRequest>
mtpRequestId sendFirst(const MTPInitConnection<TRequest> &request, RPCResponseHandler callbacks = RPCResponseHandler(), uint64 msCanWait = 0, uint32 layer = 0, bool toMainDC = false, mtpRequestId after = 0); // send first mtp request
typedef QList<MTProtoConnection*> MTProtoConnections;
MTProtoConnections connections;
@@ -269,7 +280,7 @@ private:
uint64 msSendCall, msWait;
QTimer timeouter;
QTimer sender;
SingleTimer sender;
};

View File

@@ -18,22 +18,17 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
#pragma once
template <typename TRequest>
mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler callbacks, uint64 msCanWait, uint32 layer, bool toMainDC, mtpRequestId after) {
mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler callbacks, uint64 msCanWait, bool needsLayer, bool toMainDC, mtpRequestId after) {
mtpRequestId requestId = 0;
if (layer && dc->needConnectionInit()) {
MTPInitConnection<TRequest> requestWrap(MTPinitConnection<TRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(ApiLang), request));
return sendFirst(requestWrap, callbacks, msCanWait, layer, toMainDC, after);
}
try {
uint32 requestSize = request.size() >> 2;
if (dc->connectionInited()) layer = 0;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize + (layer ? 1 : 0)));
if (layer) reqSerialized->push_back(mtpLayers[layer]);
uint32 requestSize = request.innerLength() >> 2;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize));
request.write(*reqSerialized);
DEBUG_LOG(("MTP Info: adding request to toSendMap, msCanWait %1").arg(msCanWait));
reqSerialized->msDate = getms(); // > 0 - can send without container
reqSerialized->msDate = getms(true); // > 0 - can send without container
reqSerialized->needsLayer = needsLayer;
if (after) reqSerialized->after = _mtp_internal::getRequest(after);
requestId = _mtp_internal::storeRequest(reqSerialized, callbacks);
@@ -45,44 +40,3 @@ mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler ca
if (requestId) _mtp_internal::registerRequest(requestId, toMainDC ? -getDC() : getDC());
return requestId;
}
class RPCWrappedDcDoneHandler : public RPCAbstractDoneHandler {
public:
RPCWrappedDcDoneHandler(const MTProtoDCPtr &dc, const RPCDoneHandlerPtr &ondone) : _dc(dc), _ondone(ondone) {
}
void operator()(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) const {
_dc->setConnectionInited();
if (_ondone) (*_ondone)(requestId, from, end);
}
private:
MTProtoDCPtr _dc;
RPCDoneHandlerPtr _ondone;
};
template <typename TRequest>
mtpRequestId MTProtoSession::sendFirst(const MTPInitConnection<TRequest> &request, RPCResponseHandler callbacks, uint64 msCanWait, uint32 layer, bool toMainDC, mtpRequestId after) {
mtpRequestId requestId = 0;
try {
uint32 requestSize = request.size() >> 2;
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize + (layer ? 1 : 0)));
if (layer) reqSerialized->push_back(mtpLayers[layer]);
request.write(*reqSerialized);
DEBUG_LOG(("MTP Info: adding wrapped to init connection request to toSendMap, msCanWait %1").arg(msCanWait));
callbacks.onDone = RPCDoneHandlerPtr(new RPCWrappedDcDoneHandler(dc, callbacks.onDone));
reqSerialized->msDate = getms(); // > 0 - can send without container
if (after) reqSerialized->after = _mtp_internal::getRequest(after);
requestId = _mtp_internal::storeRequest(reqSerialized, callbacks);
sendPrepared(reqSerialized, msCanWait);
} catch (Exception &e) {
requestId = 0;
_mtp_internal::rpcErrorOccured(requestId, callbacks, rpcClientError("NO_REQUEST_ID", QString("sendFirst() failed to queue request, exception: %1").arg(e.what())));
}
if (requestId) {
_mtp_internal::registerRequest(requestId, toMainDC ? -getDC() : getDC());
}
return requestId;
}

View File

@@ -13,7 +13,6 @@
/////////////////// Layer cons
///////////////////////////////
//invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
//invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;
//invokeWithLayer1#53835315 query:!X = X;
@@ -34,6 +33,7 @@
//invokeWithLayer16#cf5f0987 query:!X = X;
//invokeWithLayer17#50858a19 query:!X = X;
//invokeWithLayer18#1c900537 query:!X = X;
//invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer
///////////////////////////////
/// Authorization key creation
@@ -287,7 +287,7 @@ contactBlocked#561bc879 user_id:int date:int = ContactBlocked;
contactSuggested#3de191a1 user_id:int mutual_contacts:int = ContactSuggested;
contactStatus#aa77b873 user_id:int expires:int = ContactStatus;
contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus;
chatLocated#3631cf4c chat_id:int distance:int = ChatLocated;
@@ -510,6 +510,34 @@ contacts.found#566000e results:Vector<ContactFound> users:Vector<User> = contact
updateServiceNotification#382dd3e4 type:string message:string media:MessageMedia popup:Bool = Update;
userStatusRecently#e26f42f1 = UserStatus;
userStatusLastWeek#7bf09fc = UserStatus;
userStatusLastMonth#77ebc742 = UserStatus;
updatePrivacy#ee3b272a key:PrivacyKey rules:Vector<PrivacyRule> = Update;
inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
inputPrivacyValueAllowUsers#131cc67f users:Vector<InputUser> = InputPrivacyRule;
inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule;
inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule;
inputPrivacyValueDisallowUsers#90110467 users:Vector<InputUser> = InputPrivacyRule;
privacyValueAllowContacts#fffe1bac = PrivacyRule;
privacyValueAllowAll#65427b82 = PrivacyRule;
privacyValueAllowUsers#4d5bbe0c users:Vector<int> = PrivacyRule;
privacyValueDisallowContacts#f888fa1a = PrivacyRule;
privacyValueDisallowAll#8b73e763 = PrivacyRule;
privacyValueDisallowUsers#c7f49b7 users:Vector<int> = PrivacyRule;
account.privacyRules#554abb6f rules:Vector<PrivacyRule> users:Vector<User> = account.PrivacyRules;
accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
---functions---
invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
@@ -631,3 +659,9 @@ account.checkUsername#2714d86c username:string = Bool;
account.updateUsername#3e0bdd7c username:string = User;
contacts.search#11f812d8 q:string limit:int = contacts.Found;
account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules;
account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector<InputPrivacyRule> = account.PrivacyRules;
account.deleteAccount#418d4e0b reason:string = Bool;
account.getAccountTTL#8fc711d = AccountDaysTTL;
account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool;

View File

@@ -40,6 +40,7 @@ OverviewInner::OverviewInner(OverviewWidget *overview, ScrollArea *scroll, const
, _hist(App::history(peer->id))
, _photosInRow(1)
, _photosToAdd(0)
, _selMode(false)
, _width(0)
, _height(0)
, _minHeight(0)
@@ -425,7 +426,7 @@ void OverviewInner::dragActionFinish(const QPoint &screenPos, Qt::MouseButton bu
dragActionUpdate(screenPos);
if (textlnkOver()) {
if (textlnkDown() == textlnkOver() && _dragAction != Dragging) {
if (textlnkDown() == textlnkOver() && _dragAction != Dragging && !_selMode) {
needClick = textlnkDown();
}
}
@@ -553,10 +554,14 @@ QPixmap OverviewInner::genPix(PhotoData *photo, int32 size) {
if (!photo->full->loaded() && !photo->medium->loaded()) {
img = imageBlur(img);
}
if (img.width() > img.height()) {
img = img.scaled(img.width() * size / img.height(), size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (img.width() == img.height()) {
if (img.width() != size) {
img = img.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}
} else if (img.width() > img.height()) {
img = img.copy((img.width() - img.height()) / 2, 0, img.height(), img.height()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
} else {
img = img.scaled(size, img.height() * size / img.width(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
img = img.copy(0, (img.height() - img.width()) / 2, img.width(), img.width()).scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
}
img.setDevicePixelRatio(cRetinaFactor());
photo->forget();
@@ -625,26 +630,13 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
it->vsize = _vsize;
it->pix = genPix(photo, _vsize);
}
QPixmap &pix(it->pix);
QPoint pos(int32(i * w + st::overviewPhotoSkip), _addToY + row * (_vsize + st::overviewPhotoSkip) + st::overviewPhotoSkip);
int32 w = pix.width(), h = pix.height(), size;
if (w == h) {
p.drawPixmap(pos, pix);
size = w;
} else if (w > h) {
p.drawPixmap(pos, pix, QRect((w - h) / 2, 0, h, h));
size = h;
} else {
p.drawPixmap(pos, pix, QRect(0, (h - w) / 2, w, w));
size = w;
}
size /= cIntRetinaFactor();
p.drawPixmap(pos, it->pix);
if (!quality) {
uint64 dt = itemAnimations().animate(item, getms());
int32 cnt = int32(st::photoLoaderCnt), period = int32(st::photoLoaderPeriod), t = dt % period, delta = int32(st::photoLoaderDelta);
int32 x = pos.x() + (size - st::overviewLoader.width()) / 2, y = pos.y() + (size - st::overviewLoader.height()) / 2;
int32 x = pos.x() + (_vsize - st::overviewLoader.width()) / 2, y = pos.y() + (_vsize - st::overviewLoader.height()) / 2;
p.fillRect(x, y, st::overviewLoader.width(), st::overviewLoader.height(), st::photoLoaderBg->b);
x += (st::overviewLoader.width() - cnt * st::overviewLoaderPoint.width() - (cnt - 1) * st::overviewLoaderSkip) / 2;
y += (st::overviewLoader.height() - st::overviewLoaderPoint.height()) / 2;
@@ -671,7 +663,10 @@ void OverviewInner::paintEvent(QPaintEvent *e) {
}
}
if (sel == FullItemSel) {
p.fillRect(QRect(pos.x(), pos.y(), size, size), st::msgInSelectOverlay->b);
p.fillRect(QRect(pos.x(), pos.y(), _vsize, _vsize), st::overviewPhotoSelectOverlay->b);
p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoChecked.pxWidth(), pos.y() + _vsize - st::overviewPhotoChecked.pxHeight()), App::sprite(), st::overviewPhotoChecked);
} else if (_selMode/* || (selfrom < count && selfrom <= selto && 0 <= selto)*/) {
p.drawPixmap(QPoint(pos.x() + _vsize - st::overviewPhotoChecked.pxWidth(), pos.y() + _vsize - st::overviewPhotoChecked.pxHeight()), App::sprite(), st::overviewPhotoCheck);
}
} break;
}
@@ -1145,6 +1140,10 @@ void OverviewInner::switchType(MediaOverviewType type) {
if (App::wnd()) App::wnd()->update();
}
void OverviewInner::setSelectMode(bool enabled) {
_selMode = enabled;
}
void OverviewInner::openContextUrl() {
HistoryItem *was = App::hoveredLinkItem();
App::hoveredLinkItem(App::contextItem());
@@ -1451,6 +1450,7 @@ void OverviewInner::itemResized(HistoryItem *item) {
if (_addToY + _height - _items[i].y < _scroll->scrollTop()) {
_scroll->scrollToY(_addToY + _height - _items[i].y);
}
parentWidget()->update();
}
break;
}
@@ -1628,6 +1628,8 @@ MediaOverviewType OverviewWidget::type() const {
}
void OverviewWidget::switchType(MediaOverviewType type) {
_selCount = 0;
_inner.setSelectMode(false);
_inner.switchType(type);
switch (type) {
case OverviewPhotos: _header = lang(lng_profile_photos_header); break;
@@ -1636,7 +1638,6 @@ void OverviewWidget::switchType(MediaOverviewType type) {
case OverviewAudios: _header = lang(lng_profile_audios_header); break;
}
noSelectingScroll();
_selCount = 0;
App::main()->topBar()->showSelected(0);
updateTopBarSelection();
_scroll.scrollToY(_scroll.scrollTopMax());
@@ -1647,6 +1648,7 @@ void OverviewWidget::updateTopBarSelection() {
int32 selectedForForward, selectedForDelete;
_inner.getSelectionState(selectedForForward, selectedForDelete);
_selCount = selectedForDelete ? selectedForDelete : selectedForForward;
_inner.setSelectMode(_selCount > 0);
if (App::main()) {
App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0);
App::main()->topBar()->update();
@@ -1742,7 +1744,7 @@ void OverviewWidget::itemRemoved(HistoryItem *row) {
}
void OverviewWidget::itemResized(HistoryItem *row) {
if (row->history()->peer == peer()) {
if (!row || row->history()->peer == peer()) {
_inner.itemResized(row);
}
}
@@ -1838,6 +1840,9 @@ void OverviewWidget::onDeleteSelectedSure() {
for (SelectedItemSet::const_iterator i = sel.cbegin(), e = sel.cend(); i != e; ++i) {
i.value()->destroy();
}
if (App::main() && App::main()->peer() == peer()) {
App::main()->itemResized(0);
}
App::wnd()->hideLayer();
}
@@ -1851,6 +1856,9 @@ void OverviewWidget::onDeleteContextSure() {
MTP::send(MTPmessages_DeleteMessages(MTP_vector<MTPint>(1, MTP_int(item->id))));
}
item->destroy();
if (App::main() && App::main()->peer() == peer()) {
App::main()->itemResized(0);
}
App::wnd()->hideLayer();
}

View File

@@ -55,6 +55,8 @@ public:
MediaOverviewType type() const;
void switchType(MediaOverviewType type);
void setSelectMode(bool enabled);
void mediaOverviewUpdated();
void changingMsgId(HistoryItem *row, MsgId newId);
void msgUpdated(const HistoryItem *msg);
@@ -124,6 +126,7 @@ private:
} CachedSize;
typedef QMap<PhotoData*, CachedSize> CachedSizes;
CachedSizes _cached;
bool _selMode;
// other
typedef struct _CachedItem {

View File

@@ -340,13 +340,13 @@ void ProfileInner::reorderParticipants() {
UserData *self = App::self();
for (ChatData::Participants::const_iterator i = _peerChat->participants.cbegin(), e = _peerChat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
int32 until = user->onlineTill;
int32 until = App::onlineForSort(user->onlineTill, t);
Participants::iterator before = _participants.begin();
if (user != self) {
if (before != _participants.end() && (*before) == self) {
++before;
}
while (before != _participants.end() && (*before)->onlineTill >= until) {
while (before != _participants.end() && App::onlineForSort((*before)->onlineTill, t) >= until) {
++before;
}
}
@@ -367,7 +367,7 @@ void ProfileInner::reorderParticipants() {
} else {
_participants.clear();
if (_peerUser) {
_onlineText = App::onlineText(_peerUser->onlineTill, t, true);
_onlineText = App::onlineText(_peerUser, t, true);
} else {
_onlineText = lang(lng_chat_no_members);
}
@@ -520,7 +520,7 @@ 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->onlineTill, l_time);
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);

View File

@@ -50,7 +50,7 @@ namespace {
};
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent),
posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/iconround256.png")) {
posInited(false), trayIcon(0), trayIconMenu(0), icon256(qsl(":/gui/art/icon256.png")), wndIcon(QPixmap::fromImage(icon256)) {
connect(&psIdleTimer, SIGNAL(timeout()), this, SLOT(psIdleTimeout()));
psIdleTimer.setSingleShot(false);
}
@@ -115,6 +115,8 @@ void PsMainWindow::psUpdateWorkmode() {
}
void PsMainWindow::psUpdateCounter() {
setWindowIcon(wndIcon);
int32 counter = App::histories().unreadFull;
setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram"));

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