Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e1d5da5a1 | ||
|
|
e97cc9f172 | ||
|
|
3ce8d9f0b7 | ||
|
|
b23ffe6c94 | ||
|
|
276ef42c8f | ||
|
|
79a41d541d | ||
|
|
61e3f9000b | ||
|
|
a4c13e0720 | ||
|
|
a09460dc84 | ||
|
|
4bcfee22ef | ||
|
|
880c2697d1 | ||
|
|
188e1b61c5 | ||
|
|
696c5df092 | ||
|
|
408b38b41f | ||
|
|
4a6b6fad77 | ||
|
|
f7fa13899f | ||
|
|
f370e2b85d | ||
|
|
5d649f750b | ||
|
|
cfcf4d2336 | ||
|
|
922ab40c75 | ||
|
|
2532245413 | ||
|
|
85ca7e0f05 | ||
|
|
28c8d125cf | ||
|
|
799a81966a | ||
|
|
6333bc59b1 | ||
|
|
d953f894a1 | ||
|
|
868b9843b0 | ||
|
|
c89f13bb53 | ||
|
|
722c801f8a | ||
|
|
269e588ad0 | ||
|
|
4ee33d3bd9 | ||
|
|
85285d9862 | ||
|
|
84226635b2 | ||
|
|
8ed0cb7bf1 | ||
|
|
55649ad6c4 | ||
|
|
379c5f75e7 | ||
|
|
a75f57beb8 |
2
MSVC.md
@@ -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
|
||||
|
||||
|
||||
@@ -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!";
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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!"
|
||||
|
||||
@@ -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!";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
codesign --force --deep --sign "Developer ID Application: John Preston" ./../Mac/Release/Telegram.app
|
||||
@@ -1,4 +0,0 @@
|
||||
cd ..\Win32\Deploy
|
||||
call ..\..\..\TelegramPrivate\Sign.bat Telegram.exe
|
||||
call ..\..\..\TelegramPrivate\Sign.bat Updater.exe
|
||||
cd ..\..\Telegram
|
||||
@@ -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";
|
||||
|
||||
@@ -94,6 +94,5 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
int res = prepare(f, paths);
|
||||
system("PAUSE");
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 35 KiB |
BIN
Telegram/SourceFiles/art/icon128.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
Telegram/SourceFiles/art/icon128@2x.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Telegram/SourceFiles/art/icon16.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Telegram/SourceFiles/art/icon16@2x.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/SourceFiles/art/icon2.ico
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
Telegram/SourceFiles/art/icon256.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
Telegram/SourceFiles/art/icon256.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Telegram/SourceFiles/art/icon256@2x.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Telegram/SourceFiles/art/icon32.ico
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
Telegram/SourceFiles/art/icon32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/SourceFiles/art/icon32@2x.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Telegram/SourceFiles/art/icon48.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
Telegram/SourceFiles/art/icon48@2x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
Telegram/SourceFiles/art/icon512.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
Telegram/SourceFiles/art/icon512@2x.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
Telegram/SourceFiles/art/icon64.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
Telegram/SourceFiles/art/icon64@2x.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 655 B |
|
Before Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 183 KiB |
BIN
Telegram/SourceFiles/art/osxsetup.tif
Normal file
BIN
Telegram/SourceFiles/art/osxsetup.tiff
Normal file
BIN
Telegram/SourceFiles/art/osxsetup@2x.tif
Normal file
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 96 KiB |
@@ -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;
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -205,6 +205,7 @@ void ConnectionBox::onSave() {
|
||||
}
|
||||
App::writeConfig();
|
||||
MTP::restart();
|
||||
reinitImageLinkManager();
|
||||
emit closed();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
988
Telegram/SourceFiles/localstorage.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
102
Telegram/SourceFiles/localstorage.h
Normal 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();
|
||||
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||