Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
4ee33d3bd9 | ||
|
|
85285d9862 |
@@ -1,5 +1,5 @@
|
||||
AppVersionStr=0.6.8
|
||||
AppVersion=6008
|
||||
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.8
|
||||
AppVersion=6008
|
||||
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.8
|
||||
AppVersion=6008
|
||||
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.8
|
||||
AppVersion=6008
|
||||
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.8
|
||||
AppVersion=6008
|
||||
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.8
|
||||
AppVersion=6008
|
||||
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.8
|
||||
AppVersion=6008
|
||||
AppVersion=`./Version.sh | awk -F " " '{print $1}'`
|
||||
AppVersionStr=`./Version.sh | awk -F " " '{print $2}'`
|
||||
|
||||
echo ""
|
||||
echo "Preparing version $AppVersionStr.."
|
||||
@@ -20,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
|
||||
@@ -41,6 +36,12 @@ if [ ! -f "./../Mac/Release/Telegram.app/Contents/Frameworks/Updater" ]; then
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
cd ./../Mac/Release
|
||||
temppath=`hdiutil attach -readwrite tsetup.dmg | awk -F "\t" 'END {print $3}'`
|
||||
cp -R ./Telegram.app "$temppath/"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
@echo OFF
|
||||
|
||||
set "AppVersionStr=0.6.8"
|
||||
set "AppVersionStrSmall=0.6.13"
|
||||
set "AppVersionStr=0.6.13"
|
||||
set "AppVersionStrFull=0.6.13.0"
|
||||
|
||||
echo.
|
||||
echo Preparing version %AppVersionStr%..
|
||||
echo.
|
||||
@@ -14,7 +17,7 @@ if %errorlevel% neq 0 goto error1
|
||||
call ..\..\..\TelegramPrivate\Sign.bat Updater.exe
|
||||
if %errorlevel% neq 0 goto error1
|
||||
|
||||
iscc ..\..\Telegram\Setup.iss
|
||||
iscc /dMyAppVersion=%AppVersionStrSmall% /dMyAppVersionZero=%AppVersionStr% /dMyAppFullVersion=%AppVersionStrFull% ..\..\Telegram\Setup.iss
|
||||
if %errorlevel% neq 0 goto error1
|
||||
|
||||
call ..\..\..\TelegramPrivate\Sign.bat tsetup.%AppVersionStr%.exe
|
||||
|
||||
@@ -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;
|
||||
@@ -1637,5 +1642,5 @@ usernameCancel: flatButton(btnSelectCancel) {
|
||||
}
|
||||
|
||||
youtubeIcon: sprite(336px, 221px, 60px, 60px);
|
||||
instagramIcon: sprite(336px, 283px, 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.8"
|
||||
#define MyAppVersionZero "0.6.8"
|
||||
#define MyAppFullVersion "0.6.8.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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -114,7 +116,7 @@ namespace App {
|
||||
bool loggedOut() {
|
||||
Window *w(wnd());
|
||||
if (w) {
|
||||
w->tempDirDelete();
|
||||
w->tempDirDelete(Local::ClearManagerAll);
|
||||
w->notifyClearFast();
|
||||
w->setupIntro(true);
|
||||
}
|
||||
@@ -165,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) {
|
||||
@@ -182,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);
|
||||
}
|
||||
@@ -277,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());
|
||||
@@ -302,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);
|
||||
@@ -329,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;
|
||||
}
|
||||
@@ -408,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);
|
||||
}
|
||||
@@ -644,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);
|
||||
}
|
||||
}
|
||||
@@ -1064,7 +1109,6 @@ namespace App {
|
||||
result = new ImageLinkData(imageLink);
|
||||
imageLinksData.insert(imageLink, result);
|
||||
result->type = type;
|
||||
result->openl = TextLinkPtr(new TextLink(url));
|
||||
} else {
|
||||
result = i.value();
|
||||
}
|
||||
@@ -1242,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) {
|
||||
@@ -1615,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);
|
||||
@@ -1666,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()));
|
||||
@@ -1675,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"));
|
||||
@@ -1902,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);
|
||||
|
||||
@@ -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();
|
||||
@@ -617,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()) {
|
||||
@@ -633,7 +638,6 @@ void Application::startApp() {
|
||||
|
||||
readSupportTemplates();
|
||||
|
||||
MTP::setLayer(mtpLayerMax);
|
||||
MTP::start();
|
||||
|
||||
MTP::setStateChangedHandler(mtpStateChanged);
|
||||
@@ -766,6 +770,7 @@ Application::~Application() {
|
||||
delete window;
|
||||
|
||||
style::stopManager();
|
||||
Local::stop();
|
||||
}
|
||||
|
||||
Application *Application::app() {
|
||||
|
||||
@@ -80,6 +80,8 @@ signals:
|
||||
void peerPhotoDone(PeerId peer);
|
||||
void peerPhotoFail(PeerId peer);
|
||||
|
||||
void adjustSingleTimers();
|
||||
|
||||
public slots:
|
||||
|
||||
void startUpdateCheck(bool forceWait = false);
|
||||
@@ -139,5 +141,5 @@ private:
|
||||
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 |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
@@ -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()) {
|
||||
|
||||
@@ -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 = 6008;
|
||||
static const wchar_t *AppVersionStr = L"0.6.8";
|
||||
static const int32 AppVersion = 6013;
|
||||
static const wchar_t *AppVersionStr = L"0.6.13";
|
||||
|
||||
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)
|
||||
@@ -101,8 +101,16 @@ enum {
|
||||
|
||||
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 } };
|
||||
|
||||
@@ -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() {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -480,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));
|
||||
@@ -489,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);
|
||||
|
||||
@@ -53,6 +53,13 @@ public:
|
||||
void forget() const;
|
||||
void restore() const;
|
||||
|
||||
QByteArray savedFormat() const {
|
||||
return format;
|
||||
}
|
||||
QByteArray savedData() const {
|
||||
return saved;
|
||||
}
|
||||
|
||||
virtual ~Image() {
|
||||
invalidateSizeCache();
|
||||
}
|
||||
@@ -106,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:
|
||||
|
||||
@@ -123,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 {
|
||||
@@ -131,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);
|
||||
|
||||
@@ -27,6 +27,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
|
||||
#include "gui/filedialog.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "localstorage.h"
|
||||
|
||||
TextParseOptions _textNameOptions = {
|
||||
0, // flags
|
||||
@@ -296,12 +297,13 @@ const ChatData *PeerData::asChat() const {
|
||||
return chat ? static_cast<const ChatData *>(this) : App::chat(id | 0x100000000L);
|
||||
}
|
||||
|
||||
void PeerData::updateName(const QString &newName, const QString &newNameOrPhone) {
|
||||
if (name == newName && nameOrPhone == newNameOrPhone) return;
|
||||
void PeerData::updateName(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) {
|
||||
if (name == newName && nameOrPhone == newNameOrPhone && (chat || asUser()->username == newUsername)) return;
|
||||
|
||||
++nameVersion;
|
||||
name = newName;
|
||||
nameOrPhone = newNameOrPhone;
|
||||
if (!chat) asUser()->username = newUsername;
|
||||
Names oldNames = names;
|
||||
NameFirstChars oldChars = chars;
|
||||
fillNames();
|
||||
@@ -352,24 +354,23 @@ void PeerData::fillNames() {
|
||||
|
||||
|
||||
void UserData::setName(const QString &first, const QString &last, const QString &phoneName, const QString &usern) {
|
||||
bool updName = !first.isEmpty() || !last.isEmpty();
|
||||
|
||||
if (username != usern) {
|
||||
username = usern;
|
||||
if (App::main()) {
|
||||
App::main()->peerUsernameChanged(this);
|
||||
}
|
||||
}
|
||||
bool updName = !first.isEmpty() || !last.isEmpty(), updUsername = (username != usern);
|
||||
|
||||
if (updName && first.trimmed().isEmpty()) {
|
||||
firstName = last;
|
||||
lastName = QString();
|
||||
updateName(firstName, phoneName);
|
||||
updateName(firstName, phoneName, usern);
|
||||
} else {
|
||||
if (updName) {
|
||||
firstName = first;
|
||||
lastName = last;
|
||||
}
|
||||
updateName(firstName + ' ' + lastName, phoneName);
|
||||
updateName(firstName + ' ' + lastName, phoneName, usern);
|
||||
}
|
||||
if (updUsername) {
|
||||
if (App::main()) {
|
||||
App::main()->peerUsernameChanged(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,7 +1169,7 @@ HistoryItem *History::createItem(HistoryBlock *block, const MTPmessage &msg, boo
|
||||
case mtpc_messageActionChatEditTitle: {
|
||||
const MTPDmessageActionChatEditTitle &d(action.c_messageActionChatEditTitle());
|
||||
ChatData *chat = peer->asChat();
|
||||
if (chat) chat->updateName(qs(d.vtitle), QString());
|
||||
if (chat) chat->updateName(qs(d.vtitle), QString(), QString());
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -1755,7 +1756,7 @@ void History::clear(bool leaveItems) {
|
||||
_overview[i].clear();
|
||||
_overviewIds[i].clear();
|
||||
}
|
||||
if (App::wnd()) App::wnd()->mediaOverviewUpdated(peer);
|
||||
if (App::wnd() && !App::quiting()) App::wnd()->mediaOverviewUpdated(peer);
|
||||
for (Parent::const_iterator i = cbegin(), e = cend(); i != e; ++i) {
|
||||
if (leaveItems) {
|
||||
(*i)->clear(true);
|
||||
@@ -2059,15 +2060,15 @@ HistoryItem *regItem(HistoryItem *item, bool returnExisting) {
|
||||
return item;
|
||||
}
|
||||
|
||||
HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, int32 width) : data(App::feedPhoto(photo))
|
||||
, openl(new PhotoLink(data))
|
||||
, w(width) {
|
||||
HistoryPhoto::HistoryPhoto(const MTPDphoto &photo, int32 width) : HistoryMedia(width)
|
||||
, data(App::feedPhoto(photo))
|
||||
, openl(new PhotoLink(data)) {
|
||||
init();
|
||||
}
|
||||
|
||||
HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) : data(App::feedPhoto(photo))
|
||||
, openl(new PhotoLink(data, chat))
|
||||
, w(width) {
|
||||
HistoryPhoto::HistoryPhoto(PeerData *chat, const MTPDphoto &photo, int32 width) : HistoryMedia(width)
|
||||
, data(App::feedPhoto(photo))
|
||||
, openl(new PhotoLink(data, chat)) {
|
||||
init();
|
||||
}
|
||||
|
||||
@@ -2080,29 +2081,35 @@ void HistoryPhoto::initDimensions(const HistoryItem *parent) {
|
||||
if (!tw || !th) {
|
||||
tw = th = 1;
|
||||
}
|
||||
int32 thumbw = qMax(tw, int32(st::minPhotoWidth)), maxthumbh = thumbw;
|
||||
int32 thumbh = qRound(th * float64(thumbw) / tw);
|
||||
if (thumbh > maxthumbh) {
|
||||
thumbw = qRound(thumbw * float64(maxthumbh) / thumbh);
|
||||
thumbh = maxthumbh;
|
||||
if (thumbw < st::minPhotoWidth) {
|
||||
thumbw = st::minPhotoWidth;
|
||||
}
|
||||
}
|
||||
if (thumbh < st::minPhotoHeight) {
|
||||
thumbh = st::minPhotoHeight;
|
||||
}
|
||||
int32 thumbw = tw;
|
||||
int32 thumbh = th;
|
||||
if (!w) {
|
||||
w = thumbw;
|
||||
} else {
|
||||
thumbh = w; // square chat photo updates
|
||||
}
|
||||
_maxw = w;
|
||||
_height = _minh = thumbh;
|
||||
if (_maxw < st::minPhotoWidth) {
|
||||
_maxw = st::minPhotoWidth;
|
||||
}
|
||||
if (_height < st::minPhotoHeight) {
|
||||
_height = st::minPhotoHeight;
|
||||
}
|
||||
}
|
||||
|
||||
int32 HistoryPhoto::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
|
||||
w = width;
|
||||
w = qMin(width, _maxw);
|
||||
|
||||
int32 tw = convertScale(data->full->width()), th = convertScale(data->full->height());
|
||||
if (tw > st::maxMediaSize) {
|
||||
th = (st::maxMediaSize * th) / tw;
|
||||
tw = st::maxMediaSize;
|
||||
}
|
||||
if (th > st::maxMediaSize) {
|
||||
tw = (st::maxMediaSize * tw) / th;
|
||||
th = st::maxMediaSize;
|
||||
}
|
||||
_height = th;
|
||||
if (tw > w) {
|
||||
_height = (w * _height / tw);
|
||||
@@ -2126,6 +2133,10 @@ const QString HistoryPhoto::inDialogsText() const {
|
||||
return lang(lng_in_dlg_photo);
|
||||
}
|
||||
|
||||
const QString HistoryPhoto::inHistoryText() const {
|
||||
return qsl("[ ") + lang(lng_in_dlg_photo) + qsl(" ]");
|
||||
}
|
||||
|
||||
bool HistoryPhoto::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
return (x >= 0 && y >= 0 && x < width && y < _height);
|
||||
@@ -2143,6 +2154,40 @@ HistoryMedia *HistoryPhoto::clone() const {
|
||||
return new HistoryPhoto(*this);
|
||||
}
|
||||
|
||||
void HistoryPhoto::updateFrom(const MTPMessageMedia &media) {
|
||||
if (media.type() == mtpc_messageMediaPhoto) {
|
||||
const MTPPhoto &photo(media.c_messageMediaPhoto().vphoto);
|
||||
if (photo.type() == mtpc_photo) {
|
||||
const QVector<MTPPhotoSize> &sizes(photo.c_photo().vsizes.c_vector().v);
|
||||
for (QVector<MTPPhotoSize>::const_iterator i = sizes.cbegin(), e = sizes.cend(); i != e; ++i) {
|
||||
char size = 0;
|
||||
const MTPFileLocation *loc = 0;
|
||||
switch (i->type()) {
|
||||
case mtpc_photoSize: {
|
||||
const string &s(i->c_photoSize().vtype.c_string().v);
|
||||
loc = &i->c_photoSize().vlocation;
|
||||
if (s.size()) size = s[0];
|
||||
} break;
|
||||
|
||||
case mtpc_photoCachedSize: {
|
||||
const string &s(i->c_photoCachedSize().vtype.c_string().v);
|
||||
loc = &i->c_photoSize().vlocation;
|
||||
if (s.size()) size = s[0];
|
||||
} break;
|
||||
}
|
||||
if (!loc || loc->type() != mtpc_fileLocation) continue;
|
||||
if (size == 's') {
|
||||
Local::writeImage(storageKey(loc->c_fileLocation()), data->thumb);
|
||||
} else if (size == 'm') {
|
||||
Local::writeImage(storageKey(loc->c_fileLocation()), data->medium);
|
||||
} else if (size == 'x') {
|
||||
Local::writeImage(storageKey(loc->c_fileLocation()), data->full);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryPhoto::draw(QPainter &p, const HistoryItem *parent, bool selected, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
data->full->load(false, false);
|
||||
@@ -2254,11 +2299,11 @@ QString formatDurationAndSizeText(qint64 duration, qint64 size) {
|
||||
|
||||
int32 _downloadWidth = 0, _openWithWidth = 0, _cancelWidth = 0, _buttonWidth = 0;
|
||||
|
||||
HistoryVideo::HistoryVideo(const MTPDvideo &video, int32 width) : data(App::feedVideo(video))
|
||||
HistoryVideo::HistoryVideo(const MTPDvideo &video, int32 width) : HistoryMedia(width)
|
||||
, data(App::feedVideo(video))
|
||||
, _openl(new VideoOpenLink(data))
|
||||
, _savel(new VideoSaveLink(data))
|
||||
, _cancell(new VideoCancelLink(data))
|
||||
, w(width)
|
||||
, _dldDone(0)
|
||||
, _uplDone(0)
|
||||
{
|
||||
@@ -2304,15 +2349,14 @@ void HistoryVideo::unregItem(HistoryItem *item) {
|
||||
App::unregVideoItem(data, item);
|
||||
}
|
||||
|
||||
int32 HistoryVideo::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
|
||||
w = width;
|
||||
return _height;
|
||||
}
|
||||
|
||||
const QString HistoryVideo::inDialogsText() const {
|
||||
return lang(lng_in_dlg_video);
|
||||
}
|
||||
|
||||
const QString HistoryVideo::inHistoryText() const {
|
||||
return qsl("[ ") + lang(lng_in_dlg_video) + qsl(" ]");
|
||||
}
|
||||
|
||||
bool HistoryVideo::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
if (width >= _maxw) {
|
||||
@@ -2451,11 +2495,11 @@ void HistoryVideo::draw(QPainter &p, const HistoryItem *parent, bool selected, i
|
||||
}
|
||||
}
|
||||
|
||||
HistoryAudio::HistoryAudio(const MTPDaudio &audio, int32 width) : data(App::feedAudio(audio))
|
||||
HistoryAudio::HistoryAudio(const MTPDaudio &audio, int32 width) : HistoryMedia(width)
|
||||
, data(App::feedAudio(audio))
|
||||
, _openl(new AudioOpenLink(data))
|
||||
, _savel(new AudioSaveLink(data))
|
||||
, _cancell(new AudioCancelLink(data))
|
||||
, w(width)
|
||||
, _dldDone(0)
|
||||
, _uplDone(0)
|
||||
{
|
||||
@@ -2606,15 +2650,14 @@ void HistoryAudio::unregItem(HistoryItem *item) {
|
||||
App::unregAudioItem(data, item);
|
||||
}
|
||||
|
||||
int32 HistoryAudio::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
|
||||
w = width;
|
||||
return _height;
|
||||
}
|
||||
|
||||
const QString HistoryAudio::inDialogsText() const {
|
||||
return lang(lng_in_dlg_audio);
|
||||
}
|
||||
|
||||
const QString HistoryAudio::inHistoryText() const {
|
||||
return qsl("[ ") + lang(lng_in_dlg_audio) + qsl(" ]");
|
||||
}
|
||||
|
||||
bool HistoryAudio::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
if (width >= _maxw) {
|
||||
@@ -2650,11 +2693,11 @@ HistoryMedia *HistoryAudio::clone() const {
|
||||
return new HistoryAudio(*this);
|
||||
}
|
||||
|
||||
HistoryDocument::HistoryDocument(const MTPDdocument &document, int32 width) : data(App::feedDocument(document))
|
||||
HistoryDocument::HistoryDocument(const MTPDdocument &document, int32 width) : HistoryMedia(width)
|
||||
, data(App::feedDocument(document))
|
||||
, _openl(new DocumentOpenLink(data))
|
||||
, _savel(new DocumentSaveLink(data))
|
||||
, _cancell(new DocumentCancelLink(data))
|
||||
, w(width)
|
||||
, _name(data->name)
|
||||
, _dldDone(0)
|
||||
, _uplDone(0)
|
||||
@@ -2848,8 +2891,11 @@ void HistoryDocument::updateFrom(const MTPMessageMedia &media) {
|
||||
}
|
||||
|
||||
int32 HistoryDocument::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
|
||||
w = width;
|
||||
w = qMin(width, _maxw);
|
||||
if (parent == animated.msg) {
|
||||
if (w > st::maxMediaSize) {
|
||||
w = st::maxMediaSize;
|
||||
}
|
||||
_height = animated.h;
|
||||
if (animated.w > w) {
|
||||
_height = (w * _height / animated.w);
|
||||
@@ -2863,6 +2909,10 @@ const QString HistoryDocument::inDialogsText() const {
|
||||
return data->name.isEmpty() ? lang(lng_in_dlg_document) : data->name;
|
||||
}
|
||||
|
||||
const QString HistoryDocument::inHistoryText() const {
|
||||
return qsl("[ ") + lang(lng_in_dlg_document) + (data->name.isEmpty() ? QString() : (qsl(" : ") + data->name)) + qsl(" ]");
|
||||
}
|
||||
|
||||
bool HistoryDocument::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
if (width >= _maxw) {
|
||||
@@ -2921,8 +2971,8 @@ HistoryMedia *HistoryDocument::clone() const {
|
||||
return new HistoryDocument(*this);
|
||||
}
|
||||
|
||||
HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : userId(userId)
|
||||
, w(0)
|
||||
HistoryContact::HistoryContact(int32 userId, const QString &first, const QString &last, const QString &phone) : HistoryMedia(0)
|
||||
, userId(userId)
|
||||
, phone(App::formatPhone(phone))
|
||||
, contact(App::userLoaded(userId))
|
||||
{
|
||||
@@ -2955,7 +3005,7 @@ void HistoryContact::initDimensions(const HistoryItem *parent) {
|
||||
}
|
||||
|
||||
int32 HistoryContact::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
|
||||
w = width;
|
||||
w = qMin(width, _maxw);
|
||||
return _height;
|
||||
}
|
||||
|
||||
@@ -2963,6 +3013,10 @@ const QString HistoryContact::inDialogsText() const {
|
||||
return lang(lng_in_dlg_contact);
|
||||
}
|
||||
|
||||
const QString HistoryContact::inHistoryText() const {
|
||||
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + name.original(0, 0xFFFF, false) + qsl(", ") + phone + qsl(" ]");
|
||||
}
|
||||
|
||||
bool HistoryContact::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
return (x >= 0 && y <= 0 && x < w && y < _height);
|
||||
@@ -3061,9 +3115,10 @@ void HistoryContact::updateFrom(const MTPMessageMedia &media) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.)?youtube\\.com/watch\\?v=([a-z0-9_-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpression reInstagram(qsl("^(https?://)?(www\\.)?instagram\\.com/p/([a-z0-9_-]+)(/|$)"), QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpression reYouTube1(qsl("^(https?://)?(www\\.|m\\.)?youtube\\.com/watch\\?([^#]+&)?v=([a-z0-9_-]+)(&[^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpression reYouTube2(qsl("^(https?://)?(www\\.)?youtu\\.be/([a-z0-9_-]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpression reInstagram(qsl("^(https?://)?(www\\.)?instagram\\.com/p/([a-z0-9_-]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
|
||||
QRegularExpression reVimeo(qsl("^(https?://)?(www\\.)?vimeo\\.com/([0-9]+)([/\\?][^\\s]*)?$"), QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
ImageLinkManager manager;
|
||||
}
|
||||
@@ -3074,7 +3129,7 @@ void ImageLinkManager::init() {
|
||||
App::setProxySettings(*manager);
|
||||
|
||||
connect(manager, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)), this, SLOT(onFailed(QNetworkReply*)));
|
||||
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&errors)), this, SLOT(onFailed(QNetworkReply*)));
|
||||
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(onFailed(QNetworkReply*)));
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
|
||||
|
||||
if (black) delete black;
|
||||
@@ -3129,6 +3184,11 @@ void ImageLinkManager::getData(ImageLinkData *data) {
|
||||
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
|
||||
dataLoadings[reply] = data;
|
||||
} break;
|
||||
case VimeoLink: {
|
||||
url = qsl("https://vimeo.com/api/v2/video/") + data->id.mid(6) + qsl(".json");
|
||||
QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(url)));
|
||||
dataLoadings[reply] = data;
|
||||
} break;
|
||||
case InstagramLink: {
|
||||
//url = qsl("https://api.instagram.com/oembed?url=http://instagr.am/p/") + data->id.mid(10) + '/';
|
||||
url = qsl("https://instagram.com/p/") + data->id.mid(10) + qsl("/media/?size=l");
|
||||
@@ -3208,9 +3268,9 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) {
|
||||
DEBUG_LOG(("JSON Error: Bad json received in onFinished() for image link"));
|
||||
return onFailed(reply);
|
||||
}
|
||||
QJsonObject obj = doc.object();
|
||||
switch (d->type) {
|
||||
case YouTubeLink: {
|
||||
QJsonObject obj = doc.object();
|
||||
QString thumb;
|
||||
int32 seconds = 0;
|
||||
QJsonObject::const_iterator entryIt = obj.constFind(qsl("entry"));
|
||||
@@ -3306,6 +3366,39 @@ void ImageLinkManager::onFinished(QNetworkReply *reply) {
|
||||
imageLoadings.insert(manager->get(QNetworkRequest(thumb)), d);
|
||||
}
|
||||
} break;
|
||||
|
||||
case VimeoLink: {
|
||||
QString thumb;
|
||||
int32 seconds = 0;
|
||||
QJsonArray arr = doc.array();
|
||||
if (!arr.isEmpty()) {
|
||||
QJsonObject obj = arr.at(0).toObject();
|
||||
QJsonObject::const_iterator titleIt = obj.constFind(qsl("title"));
|
||||
if (titleIt != obj.constEnd() && titleIt.value().isString()) {
|
||||
d->title = titleIt.value().toString();
|
||||
}
|
||||
QJsonObject::const_iterator thumbnailsIt = obj.constFind(qsl("thumbnail_large"));
|
||||
if (thumbnailsIt != obj.constEnd() && thumbnailsIt.value().isString()) {
|
||||
thumb = thumbnailsIt.value().toString();
|
||||
}
|
||||
QJsonObject::const_iterator secondsIt = obj.constFind(qsl("duration"));
|
||||
if (secondsIt != obj.constEnd()) {
|
||||
if (secondsIt.value().isDouble()) {
|
||||
seconds = qRound(secondsIt.value().toDouble());
|
||||
} else if (secondsIt.value().isString()) {
|
||||
seconds = qRound(secondsIt.value().toString().toDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (seconds > 0) {
|
||||
d->duration = formatDurationText(seconds);
|
||||
}
|
||||
if (thumb.isEmpty()) {
|
||||
failed(d);
|
||||
} else {
|
||||
imageLoadings.insert(manager->get(QNetworkRequest(thumb)), d);
|
||||
}
|
||||
} break;
|
||||
|
||||
case InstagramLink: failed(d); break;
|
||||
case GoogleMapsLink: failed(d); break;
|
||||
@@ -3372,20 +3465,35 @@ void ImageLinkData::load() {
|
||||
manager.getData(this);
|
||||
}
|
||||
|
||||
HistoryImageLink::HistoryImageLink(const QString &url, int32 width) : w(width) {
|
||||
HistoryImageLink::HistoryImageLink(const QString &url, int32 width) : HistoryMedia(width) {
|
||||
if (url.startsWith(qsl("location:"))) {
|
||||
data = App::imageLink(url, GoogleMapsLink, qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17"));
|
||||
QString lnk = qsl("https://maps.google.com/maps?q=") + url.mid(9) + qsl("&ll=") + url.mid(9) + qsl("&z=17");
|
||||
link.reset(new TextLink(lnk));
|
||||
|
||||
data = App::imageLink(url, GoogleMapsLink, lnk);
|
||||
} else {
|
||||
link.reset(new TextLink(url));
|
||||
|
||||
int matchIndex = 4;
|
||||
QRegularExpressionMatch m = reYouTube1.match(url);
|
||||
if (!m.hasMatch()) m = reYouTube2.match(url);
|
||||
if (!m.hasMatch()) {
|
||||
m = reYouTube2.match(url);
|
||||
matchIndex = 3;
|
||||
}
|
||||
if (m.hasMatch()) {
|
||||
data = App::imageLink(qsl("youtube:") + m.captured(3), YouTubeLink, url);
|
||||
data = App::imageLink(qsl("youtube:") + m.captured(matchIndex), YouTubeLink, url);
|
||||
} else {
|
||||
m = reInstagram.match(url);
|
||||
m = reVimeo.match(url);
|
||||
if (m.hasMatch()) {
|
||||
data = App::imageLink(qsl("instagram:") + m.captured(3), InstagramLink, url);
|
||||
data = App::imageLink(qsl("vimeo:") + m.captured(3), VimeoLink, url);
|
||||
} else {
|
||||
data = 0;
|
||||
m = reInstagram.match(url);
|
||||
if (m.hasMatch()) {
|
||||
data = App::imageLink(qsl("instagram:") + m.captured(3), InstagramLink, url);
|
||||
data->title = qsl("instagram.com/p/") + m.captured(3);
|
||||
} else {
|
||||
data = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3395,6 +3503,7 @@ int32 HistoryImageLink::fullWidth() const {
|
||||
if (data) {
|
||||
switch (data->type) {
|
||||
case YouTubeLink: return 640;
|
||||
case VimeoLink: return 640;
|
||||
case InstagramLink: return 640;
|
||||
case GoogleMapsLink: return st::locationSize.width();
|
||||
}
|
||||
@@ -3406,6 +3515,7 @@ int32 HistoryImageLink::fullHeight() const {
|
||||
if (data) {
|
||||
switch (data->type) {
|
||||
case YouTubeLink: return 480;
|
||||
case VimeoLink: return 480;
|
||||
case InstagramLink: return 640;
|
||||
case GoogleMapsLink: return st::locationSize.height();
|
||||
}
|
||||
@@ -3460,7 +3570,7 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte
|
||||
if (data) {
|
||||
switch (data->type) {
|
||||
case YouTubeLink: p.drawPixmap(QPoint((width - st::youtubeIcon.pxWidth()) / 2, (_height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::youtubeIcon); break;
|
||||
case InstagramLink: p.drawPixmap(QPoint((width - st::instagramIcon.pxWidth()) / 2, (_height - st::instagramIcon.pxHeight()) / 2), App::sprite(), st::instagramIcon); break;
|
||||
case VimeoLink: p.drawPixmap(QPoint((width - st::youtubeIcon.pxWidth()) / 2, (_height - st::youtubeIcon.pxHeight()) / 2), App::sprite(), st::vimeoIcon); break;
|
||||
}
|
||||
if (!data->title.isEmpty() || !data->duration.isEmpty()) {
|
||||
p.fillRect(0, 0, width, st::msgDateFont->height + 2 * st::msgDateImgPadding.y(), st::msgDateImgBg->b);
|
||||
@@ -3515,9 +3625,13 @@ void HistoryImageLink::draw(QPainter &p, const HistoryItem *parent, bool selecte
|
||||
}
|
||||
|
||||
int32 HistoryImageLink::resize(int32 width, bool dontRecountText, const HistoryItem *parent) {
|
||||
w = width;
|
||||
w = qMin(width, _maxw);
|
||||
|
||||
int32 tw = convertScale(fullWidth()), th = convertScale(fullHeight());
|
||||
if (tw > st::maxMediaSize) {
|
||||
th = (st::maxMediaSize * th) / tw;
|
||||
tw = st::maxMediaSize;
|
||||
}
|
||||
_height = th;
|
||||
if (tw > w) {
|
||||
_height = (w * _height / tw);
|
||||
@@ -3541,6 +3655,7 @@ const QString HistoryImageLink::inDialogsText() const {
|
||||
if (data) {
|
||||
switch (data->type) {
|
||||
case YouTubeLink: return qsl("YouTube Video");
|
||||
case VimeoLink: return qsl("Vimeo Video");
|
||||
case InstagramLink: return qsl("Instagram Link");
|
||||
case GoogleMapsLink: return lang(lng_maps_point);
|
||||
}
|
||||
@@ -3548,6 +3663,18 @@ const QString HistoryImageLink::inDialogsText() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QString HistoryImageLink::inHistoryText() const {
|
||||
if (data) {
|
||||
switch (data->type) {
|
||||
case YouTubeLink: return qsl("[ YouTube Video : ") + link->text() + qsl(" ]");
|
||||
case VimeoLink: return qsl("[ Vimeo Video : ") + link->text() + qsl(" ]");
|
||||
case InstagramLink: return qsl("[ Instagram Link : ") + link->text() + qsl(" ]");
|
||||
case GoogleMapsLink: return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + link->text() + qsl(" ]");
|
||||
}
|
||||
}
|
||||
return qsl("[ Link : ") + link->text() + qsl(" ]");
|
||||
}
|
||||
|
||||
bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
return (x >= 0 && y >= 0 && x < width && y < _height);
|
||||
@@ -3556,7 +3683,7 @@ bool HistoryImageLink::hasPoint(int32 x, int32 y, const HistoryItem *parent, int
|
||||
TextLinkPtr HistoryImageLink::getLink(int32 x, int32 y, const HistoryItem *parent, int32 width) const {
|
||||
if (width < 0) width = w;
|
||||
if (x >= 0 && y >= 0 && x < width && y < _height && data) {
|
||||
return data->openl;
|
||||
return link;
|
||||
}
|
||||
return TextLinkPtr();
|
||||
}
|
||||
@@ -3620,7 +3747,7 @@ void HistoryMessage::initMedia(const MTPMessageMedia &media, QString ¤tTex
|
||||
switch (media.type()) {
|
||||
case mtpc_messageMediaEmpty: {
|
||||
QString lnk = currentText.trimmed();
|
||||
if (reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch()) {
|
||||
if (reYouTube1.match(currentText).hasMatch() || reYouTube2.match(currentText).hasMatch() || reInstagram.match(currentText).hasMatch() || reVimeo.match(currentText).hasMatch()) {
|
||||
_media = new HistoryImageLink(lnk);
|
||||
currentText = QString();
|
||||
}
|
||||
@@ -3701,7 +3828,7 @@ bool HistoryMessage::uploading() const {
|
||||
|
||||
QString HistoryMessage::selectedText(uint32 selection) const {
|
||||
if (_media && selection == FullItemSel) {
|
||||
return _text.original(0, 0xFFFF) + '[' + _media->inDialogsText() + ']';
|
||||
return _text.original(0, 0xFFFF) + _media->inHistoryText();
|
||||
}
|
||||
uint16 selectedFrom = (selection == FullItemSel) ? 0 : (selection >> 16) & 0xFFFF;
|
||||
uint16 selectedTo = (selection == FullItemSel) ? 0xFFFF : (selection & 0xFFFF);
|
||||
@@ -3736,7 +3863,10 @@ void HistoryMessage::draw(QPainter &p, uint32 selection) const {
|
||||
_fromVersion = _from->nameVersion;
|
||||
}
|
||||
int32 left = _out ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth;
|
||||
if (_media && _media->maxWidth() > mwidth) mwidth = _media->maxWidth();
|
||||
if (_media) {
|
||||
if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth();
|
||||
if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth();
|
||||
}
|
||||
if (width > mwidth) {
|
||||
if (_out) left += width - mwidth;
|
||||
width = mwidth;
|
||||
@@ -3842,7 +3972,10 @@ int32 HistoryMessage::resize(int32 width, bool dontRecountText, const HistoryIte
|
||||
|
||||
bool HistoryMessage::hasPoint(int32 x, int32 y) const {
|
||||
int32 left = _out ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth;
|
||||
if (_media && _media->maxWidth() > mwidth) mwidth = _media->maxWidth();
|
||||
if (_media) {
|
||||
if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth();
|
||||
if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth();
|
||||
}
|
||||
if (width > mwidth) {
|
||||
if (_out) left += width - mwidth;
|
||||
width = mwidth;
|
||||
@@ -3869,7 +4002,10 @@ void HistoryMessage::getState(TextLinkPtr &lnk, bool &inText, int32 x, int32 y)
|
||||
lnk = TextLinkPtr();
|
||||
|
||||
int32 left = _out ? st::msgMargin.right() : st::msgMargin.left(), width = _history->width - st::msgMargin.left() - st::msgMargin.right(), mwidth = st::msgMaxWidth;
|
||||
if (_media && _media->maxWidth() > mwidth) mwidth = _media->maxWidth();
|
||||
if (_media) {
|
||||
if (_media->maxWidth() > mwidth) mwidth = _media->maxWidth();
|
||||
if (_media->currentWidth() < mwidth) mwidth = _media->currentWidth();
|
||||
}
|
||||
if (width > mwidth) {
|
||||
if (_out) left += width - mwidth;
|
||||
width = mwidth;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -629,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;
|
||||
@@ -732,7 +760,7 @@ struct History : public QList<HistoryBlock*> {
|
||||
}
|
||||
|
||||
QString draft;
|
||||
QTextCursor draftCur;
|
||||
MessageCursor draftCursor;
|
||||
int32 lastWidth, lastScrollTop;
|
||||
bool mute;
|
||||
|
||||
@@ -1218,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 {
|
||||
@@ -1244,6 +1280,14 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 currentWidth() const {
|
||||
return w;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
int32 w;
|
||||
|
||||
};
|
||||
|
||||
class HistoryPhoto : public HistoryMedia {
|
||||
@@ -1261,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;
|
||||
@@ -1269,6 +1314,8 @@ public:
|
||||
return data;
|
||||
}
|
||||
|
||||
void updateFrom(const MTPMessageMedia &media);
|
||||
|
||||
TextLinkPtr lnk() const {
|
||||
return openl;
|
||||
}
|
||||
@@ -1281,7 +1328,7 @@ public:
|
||||
private:
|
||||
PhotoData *data;
|
||||
TextLinkPtr openl;
|
||||
int32 w;
|
||||
|
||||
};
|
||||
|
||||
QString formatSizeText(qint64 size);
|
||||
@@ -1293,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 {
|
||||
@@ -1311,8 +1358,7 @@ public:
|
||||
private:
|
||||
VideoData *data;
|
||||
TextLinkPtr _openl, _savel, _cancell;
|
||||
int32 w;
|
||||
|
||||
|
||||
QString _size;
|
||||
int32 _thumbw, _thumbx, _thumby;
|
||||
|
||||
@@ -1327,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 {
|
||||
@@ -1345,7 +1391,6 @@ public:
|
||||
private:
|
||||
AudioData *data;
|
||||
TextLinkPtr _openl, _savel, _cancell;
|
||||
int32 w;
|
||||
|
||||
QString _size;
|
||||
|
||||
@@ -1365,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 {
|
||||
@@ -1386,7 +1432,6 @@ private:
|
||||
|
||||
DocumentData *data;
|
||||
TextLinkPtr _openl, _savel, _cancell;
|
||||
int32 w;
|
||||
|
||||
int32 _namew;
|
||||
QString _name, _size;
|
||||
@@ -1408,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;
|
||||
@@ -1416,7 +1462,7 @@ public:
|
||||
|
||||
private:
|
||||
int32 userId;
|
||||
int32 w, phonew;
|
||||
int32 phonew;
|
||||
Text name;
|
||||
QString phone;
|
||||
UserData *contact;
|
||||
@@ -1429,6 +1475,7 @@ void deinitImageLinkManager();
|
||||
enum ImageLinkType {
|
||||
InvalidImageLink = 0,
|
||||
YouTubeLink,
|
||||
VimeoLink,
|
||||
InstagramLink,
|
||||
GoogleMapsLink
|
||||
};
|
||||
@@ -1439,7 +1486,6 @@ struct ImageLinkData {
|
||||
QString id;
|
||||
QString title, duration;
|
||||
ImagePtr thumb;
|
||||
TextLinkPtr openl;
|
||||
ImageLinkType type;
|
||||
bool loading;
|
||||
|
||||
@@ -1488,13 +1534,15 @@ public:
|
||||
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;
|
||||
int32 w;
|
||||
TextLinkPtr link;
|
||||
|
||||
};
|
||||
|
||||
class HistoryMessage : public HistoryItem {
|
||||
|
||||
@@ -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() {
|
||||
@@ -1608,7 +1654,7 @@ void HistoryWidget::cancelTyping() {
|
||||
|
||||
void HistoryWidget::updateTyping(bool typing) {
|
||||
uint64 ms = getms(true) + 10000;
|
||||
if (noTypingUpdate || !hist || (typing && (hist->myTyping + 5000 > ms)) || (!typing && (hist->myTyping + 5000 <= ms))) return;
|
||||
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 {
|
||||
@@ -1795,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()));
|
||||
@@ -2215,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();
|
||||
}
|
||||
@@ -2680,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;
|
||||
@@ -3156,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) {
|
||||
|
||||
@@ -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);
|
||||
@@ -766,12 +764,15 @@ void MainWidget::peerUsernameChanged(PeerData *peer) {
|
||||
|
||||
void MainWidget::checkLastUpdate(bool afterSleep) {
|
||||
uint64 n = getms(true);
|
||||
LOG(("Checking last update!.. last update %1, now %2, noUpdatesTimer %3, remains %4").arg(_lastUpdateTime).arg(n).arg(noUpdatesTimer.isActive() ? 1 : 0).arg(noUpdatesTimer.remainingTime()));
|
||||
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;
|
||||
@@ -1731,7 +1732,6 @@ void MainWidget::gotState(const MTPupdates_State &state) {
|
||||
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
|
||||
_lastUpdateTime = getms(true);
|
||||
noUpdatesTimer.start(NoUpdatesTimeout);
|
||||
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
|
||||
updInited = true;
|
||||
|
||||
dialogs.loadDialogs();
|
||||
@@ -1749,7 +1749,7 @@ void MainWidget::gotDifference(const MTPupdates_Difference &diff) {
|
||||
MTP::setGlobalDoneHandler(rpcDone(&MainWidget::updateReceived));
|
||||
_lastUpdateTime = getms(true);
|
||||
noUpdatesTimer.start(NoUpdatesTimeout);
|
||||
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
|
||||
|
||||
updInited = true;
|
||||
} break;
|
||||
case mtpc_updates_differenceSlice: {
|
||||
@@ -1997,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);
|
||||
@@ -2040,7 +2042,6 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
|
||||
|
||||
_lastUpdateTime = getms(true);
|
||||
noUpdatesTimer.start(NoUpdatesTimeout);
|
||||
LOG(("Started no updates timeout, %1").arg(_lastUpdateTime));
|
||||
|
||||
handleUpdates(updates);
|
||||
} catch(mtpErrorUnexpected &e) { // just some other type
|
||||
@@ -2252,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;
|
||||
}
|
||||
@@ -2263,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;
|
||||
|
||||
@@ -285,6 +285,7 @@ public:
|
||||
void peerUsernameChanged(PeerData *peer);
|
||||
|
||||
void checkLastUpdate(bool afterSleep);
|
||||
void showNewGroup();
|
||||
|
||||
~MainWidget();
|
||||
|
||||
|
||||
@@ -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,7 +470,6 @@ void MediaView::showPhoto(PhotoData *photo) {
|
||||
_doc = 0;
|
||||
_zoom = 0;
|
||||
MTP::clearLoaderPriorities();
|
||||
_photo->full->load();
|
||||
_full = -1;
|
||||
_current = QPixmap();
|
||||
_down = OverNone;
|
||||
@@ -490,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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -563,31 +564,11 @@ namespace _mtp_internal {
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -624,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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -65,9 +63,6 @@ namespace _mtp_internal {
|
||||
|
||||
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,
|
||||
@@ -86,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);
|
||||
@@ -95,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);
|
||||
|
||||
|
||||
@@ -309,8 +309,8 @@ void MTProtoConnection::restart() {
|
||||
}
|
||||
|
||||
void MTProtoConnection::stop() {
|
||||
data->stop();
|
||||
thread->quit();
|
||||
if (data) data->stop();
|
||||
if (thread) thread->quit();
|
||||
}
|
||||
|
||||
void MTProtoConnection::stopped() {
|
||||
@@ -473,7 +473,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);
|
||||
@@ -604,7 +604,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);
|
||||
@@ -613,6 +613,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));
|
||||
@@ -627,7 +630,7 @@ void MTPautoConnection::onHttpStart() {
|
||||
if (status == HttpReady) {
|
||||
DEBUG_LOG(("Connection Info: Http-transport chosen by timer"));
|
||||
status = UsingHttp;
|
||||
sock.disconnect();
|
||||
sock.disconnectFromHost();
|
||||
emit connected();
|
||||
}
|
||||
}
|
||||
@@ -638,13 +641,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) {
|
||||
@@ -680,15 +707,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) {
|
||||
@@ -725,14 +743,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() {
|
||||
@@ -766,7 +787,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();
|
||||
}
|
||||
}
|
||||
@@ -786,7 +807,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) {
|
||||
@@ -802,14 +823,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));
|
||||
}
|
||||
@@ -817,6 +839,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());
|
||||
@@ -828,11 +851,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();
|
||||
@@ -907,14 +930,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);
|
||||
@@ -1002,7 +1026,7 @@ void MTPhttpConnection::requestFinished(QNetworkReply *reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool mayBeBadKey = _handleHttpError(reply);
|
||||
bool mayBeBadKey = _handleHttpError(reply) && _sentEncrypted;
|
||||
|
||||
emit error(mayBeBadKey);
|
||||
}
|
||||
@@ -1050,7 +1074,7 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
|
||||
, conn(0)
|
||||
, retryTimeout(1)
|
||||
, oldConnection(true)
|
||||
, receiveDelay(MinReceiveDelay)
|
||||
, receiveDelay(MTPMinReceiveDelay)
|
||||
, firstSentAt(-1)
|
||||
, pingId(0)
|
||||
, toSendPingId(0)
|
||||
@@ -1094,7 +1118,9 @@ MTProtoConnectionPrivate::MTProtoConnectionPrivate(QThread *thread, MTProtoConne
|
||||
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(needToSendAsync()), sessionData->owner(), SIGNAL(needToSend()));
|
||||
connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()));
|
||||
connect(this, SIGNAL(sendAnythingAsync(quint64)), sessionData->owner(), SLOT(sendAnything(quint64)));
|
||||
}
|
||||
|
||||
void MTProtoConnectionPrivate::onConfigLoaded() {
|
||||
@@ -1362,6 +1388,7 @@ void MTProtoConnectionPrivate::tryToSend() {
|
||||
return;
|
||||
}
|
||||
|
||||
bool needsLayer = !sessionData->layerWasInited();
|
||||
bool prependOnly = false;
|
||||
mtpRequest pingRequest;
|
||||
if (toSendPingId) {
|
||||
@@ -1370,7 +1397,7 @@ 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);
|
||||
|
||||
@@ -1381,7 +1408,7 @@ void MTProtoConnectionPrivate::tryToSend() {
|
||||
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,7 +1418,7 @@ 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(true); // > 0 - can send without container
|
||||
@@ -1402,7 +1429,7 @@ 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(true); // > 0 - can send without container
|
||||
@@ -1426,7 +1453,7 @@ 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(true); // > 0 - can send without container
|
||||
@@ -1434,6 +1461,14 @@ void MTProtoConnectionPrivate::tryToSend() {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
{
|
||||
@@ -1473,14 +1508,27 @@ void MTProtoConnectionPrivate::tryToSend() {
|
||||
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 +1537,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 +1545,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);
|
||||
@@ -1530,8 +1590,20 @@ void MTProtoConnectionPrivate::tryToSend() {
|
||||
if (req->requestId) {
|
||||
if (mtpRequestData::needAck(req)) {
|
||||
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);
|
||||
@@ -1695,14 +1767,14 @@ void MTProtoConnectionPrivate::onReceivedSome() {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1881,7 +1953,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;
|
||||
@@ -1909,7 +1981,7 @@ void MTProtoConnectionPrivate::handleReceived() {
|
||||
|
||||
if (!wasConnected) {
|
||||
if (getState() == MTProtoConnection::Connected) {
|
||||
emit sessionData->owner()->needToSendAsync();
|
||||
emit needToSendAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2329,6 +2401,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)) {
|
||||
@@ -2787,7 +2863,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);
|
||||
@@ -2854,7 +2930,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);
|
||||
@@ -2979,7 +3055,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);
|
||||
}
|
||||
@@ -3047,7 +3123,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;
|
||||
@@ -3116,7 +3192,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()));
|
||||
@@ -3146,7 +3222,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);
|
||||
}
|
||||
|
||||
@@ -3157,7 +3233,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);
|
||||
@@ -3264,6 +3340,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) {
|
||||
|
||||
@@ -106,6 +106,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 +142,7 @@ signals:
|
||||
protected:
|
||||
|
||||
BuffersQueue receivedQueue; // list of received packets, not processed yet
|
||||
bool _sentEncrypted;
|
||||
|
||||
};
|
||||
|
||||
@@ -189,6 +197,8 @@ public slots:
|
||||
void onSocketDisconnected();
|
||||
void onHttpStart();
|
||||
|
||||
void onTcpTimeoutTimer();
|
||||
|
||||
protected:
|
||||
|
||||
void socketPacket(mtpPrime *packet, uint32 packetSize);
|
||||
@@ -215,6 +225,10 @@ private:
|
||||
typedef QSet<QNetworkReply*> Requests;
|
||||
Requests requests;
|
||||
|
||||
QString _addr;
|
||||
int32 _port, _tcpTimeout;
|
||||
QTimer tcpTimeoutTimer;
|
||||
|
||||
};
|
||||
|
||||
class MTPtcpConnection : public MTPabstractTcpConnection {
|
||||
@@ -295,6 +309,8 @@ signals:
|
||||
void needToRestart();
|
||||
void stateChanged(qint32 newState);
|
||||
void sessionResetDone();
|
||||
void needToSendAsync();
|
||||
void sendAnythingAsync(quint64);
|
||||
|
||||
public slots:
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,9 +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()));
|
||||
|
||||
MTProtoDCMap &dcs(mtpDCMap());
|
||||
|
||||
@@ -113,8 +110,11 @@ 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,13 +126,13 @@ void MTProtoSession::restart() {
|
||||
}
|
||||
|
||||
void MTProtoSession::stop() {
|
||||
while (connections.size()) {
|
||||
while (!connections.isEmpty()) {
|
||||
connections.back()->stop();
|
||||
connections.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoSession::sendAnything(uint64 msCanWait) {
|
||||
void MTProtoSession::sendAnything(quint64 msCanWait) {
|
||||
uint64 ms = getms(true);
|
||||
if (msSendCall) {
|
||||
if (ms > msSendCall + msWait) {
|
||||
@@ -147,13 +147,14 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,21 +377,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(true); // > 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 {
|
||||
@@ -403,11 +392,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;
|
||||
@@ -239,25 +252,21 @@ 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();
|
||||
|
||||
public slots:
|
||||
|
||||
void authKeyCreatedForDC();
|
||||
void layerWasInitedForDC(bool wasInited);
|
||||
|
||||
void tryToReceive();
|
||||
void checkRequestsByTimer();
|
||||
void onConnectionStateChange(qint32 newState);
|
||||
void onResetDone();
|
||||
|
||||
void sendAnything(quint64 msCanWait);
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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(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(true); // > 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());
|
||||
@@ -1629,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;
|
||||
@@ -1637,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());
|
||||
@@ -1648,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();
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -88,6 +88,7 @@ protected:
|
||||
QSystemTrayIcon *trayIcon;
|
||||
QMenu *trayIconMenu;
|
||||
QImage icon256;
|
||||
QIcon wndIcon;
|
||||
|
||||
virtual void setupTrayIcon() = 0;
|
||||
virtual QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon) = 0;
|
||||
|
||||
@@ -21,6 +21,7 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
|
||||
#include "lang.h"
|
||||
#include "application.h"
|
||||
#include "mainwidget.h"
|
||||
#include "historywidget.h"
|
||||
|
||||
namespace {
|
||||
bool frameless = true;
|
||||
@@ -48,6 +49,12 @@ void MacPrivate::activeSpaceChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
void MacPrivate::darkModeChanged() {
|
||||
if (App::wnd()) {
|
||||
App::wnd()->psUpdateCounter();
|
||||
}
|
||||
}
|
||||
|
||||
void MacPrivate::notifyClicked(unsigned long long peer) {
|
||||
History *history = App::history(PeerId(peer));
|
||||
|
||||
@@ -64,7 +71,8 @@ void MacPrivate::notifyReplied(unsigned long long peer, const char *str) {
|
||||
}
|
||||
|
||||
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(qsl(":/gui/art/icon256.png"))),
|
||||
psLogout(0), psUndo(0), psRedo(0), psCut(0), psCopy(0), psPaste(0), psDelete(0), psSelectAll(0), psContacts(0), psNewGroup(0), psShowTelegram(0) {
|
||||
QImage tray(qsl(":/gui/art/osxtray.png"));
|
||||
trayImg = tray.copy(0, cRetina() ? 0 : tray.width() / 2, tray.width() / (cRetina() ? 2 : 4), tray.width() / (cRetina() ? 2 : 4));
|
||||
trayImgSel = tray.copy(tray.width() / (cRetina() ? 2 : 4), cRetina() ? 0 : tray.width() / 2, tray.width() / (cRetina() ? 2 : 4), tray.width() / (cRetina() ? 2 : 4));
|
||||
@@ -141,6 +149,7 @@ void PsMainWindow::psUpdateWorkmode() {
|
||||
}
|
||||
trayIcon = 0;
|
||||
}
|
||||
setWindowIcon(wndIcon);
|
||||
}
|
||||
|
||||
void _placeCounter(QImage &img, int size, int count, style::color bg, style::color color) {
|
||||
@@ -182,18 +191,20 @@ void PsMainWindow::psUpdateCounter() {
|
||||
int32 counter = App::histories().unreadFull;
|
||||
|
||||
setWindowTitle((counter > 0) ? qsl("Telegram (%1)").arg(counter) : qsl("Telegram"));
|
||||
setWindowIcon(wndIcon);
|
||||
|
||||
QString cnt = (counter < 1000) ? QString("%1").arg(counter) : QString("..%1").arg(counter % 100, 2, 10, QChar('0'));
|
||||
_private.setWindowBadge(counter ? cnt : QString());
|
||||
|
||||
if (trayIcon) {
|
||||
style::color bg = (App::histories().unreadMuted < counter) ? st::counterBG : st::counterMuteBG;
|
||||
bool dm = objc_darkMode(), important = (App::histories().unreadMuted < counter);
|
||||
style::color bg = important ? st::counterBG : st::counterMuteBG;
|
||||
QIcon icon;
|
||||
QImage img(psTrayIcon()), imgsel(psTrayIcon(true));
|
||||
QImage img(psTrayIcon(dm)), imgsel(psTrayIcon(true));
|
||||
img.detach();
|
||||
imgsel.detach();
|
||||
int32 size = cRetina() ? 44 : 22;
|
||||
_placeCounter(img, size, counter, bg, st::counterColor);
|
||||
_placeCounter(img, size, counter, bg, (dm && !important) ? st::counterMacInvColor : st::counterColor);
|
||||
_placeCounter(imgsel, size, counter, st::white, st::counterMacInvColor);
|
||||
icon.addPixmap(QPixmap::fromImage(img));
|
||||
icon.addPixmap(QPixmap::fromImage(imgsel), QIcon::Selected);
|
||||
@@ -335,6 +346,81 @@ void PsMainWindow::psFirstShow() {
|
||||
show();
|
||||
}
|
||||
posInited = true;
|
||||
|
||||
// init global menu
|
||||
QMenu *main = psMainMenu.addMenu(qsl("Telegram"));
|
||||
main->addAction(lang(lng_mac_menu_about), App::wnd()->getTitle(), SLOT(onAbout()))->setMenuRole(QAction::AboutQtRole);
|
||||
main->addSeparator();
|
||||
QAction *prefs = main->addAction(lang(lng_mac_menu_preferences), App::wnd(), SLOT(showSettings()));
|
||||
prefs->setMenuRole(QAction::PreferencesRole);
|
||||
|
||||
QMenu *file = psMainMenu.addMenu(lang(lng_mac_menu_file));
|
||||
psLogout = file->addAction(lang(lng_mac_menu_logout), App::wnd(), SLOT(onLogout()));
|
||||
|
||||
QMenu *edit = psMainMenu.addMenu(lang(lng_mac_menu_edit));
|
||||
psUndo = edit->addAction(lang(lng_mac_menu_undo), this, SLOT(psMacUndo()), QKeySequence::Undo);
|
||||
psRedo = edit->addAction(lang(lng_mac_menu_redo), this, SLOT(psMacRedo()), QKeySequence::Redo);
|
||||
edit->addSeparator();
|
||||
psCut = edit->addAction(lang(lng_mac_menu_cut), this, SLOT(psMacCut()), QKeySequence::Cut);
|
||||
psCopy = edit->addAction(lang(lng_mac_menu_copy), this, SLOT(psMacCopy()), QKeySequence::Copy);
|
||||
psPaste = edit->addAction(lang(lng_mac_menu_paste), this, SLOT(psMacPaste()), QKeySequence::Paste);
|
||||
psDelete = edit->addAction(lang(lng_mac_menu_delete), this, SLOT(psMacDelete()), QKeySequence(Qt::ControlModifier | Qt::Key_Backspace));
|
||||
edit->addSeparator();
|
||||
psSelectAll = edit->addAction(lang(lng_mac_menu_select_all), this, SLOT(psMacSelectAll()), QKeySequence::SelectAll);
|
||||
|
||||
QMenu *window = psMainMenu.addMenu(lang(lng_mac_menu_window));
|
||||
psContacts = window->addAction(lang(lng_mac_menu_contacts), App::wnd()->getTitle(), SLOT(onContacts()));
|
||||
window->addSeparator();
|
||||
psNewGroup = window->addAction(lang(lng_mac_menu_new_group), App::wnd(), SLOT(onShowNewGroup()));
|
||||
window->addSeparator();
|
||||
psShowTelegram = window->addAction(lang(lng_mac_menu_show), App::wnd(), SLOT(showFromTray()));
|
||||
|
||||
psMacUpdateMenu();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void _sendKeySequence(Qt::Key key, Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
|
||||
QWidget *focused = QApplication::focusWidget();
|
||||
if (qobject_cast<QLineEdit*>(focused) || qobject_cast<FlatTextarea*>(focused) || qobject_cast<HistoryList*>(focused)) {
|
||||
QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyPress, key, modifiers));
|
||||
QApplication::postEvent(focused, new QKeyEvent(QEvent::KeyRelease, key, modifiers));
|
||||
}
|
||||
}
|
||||
void _forceDisabled(QAction *action, bool disabled) {
|
||||
if (action->isEnabled()) {
|
||||
if (disabled) action->setDisabled(true);
|
||||
} else if (!disabled) {
|
||||
action->setDisabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacUndo() {
|
||||
_sendKeySequence(Qt::Key_Z, Qt::ControlModifier);
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacRedo() {
|
||||
_sendKeySequence(Qt::Key_Z, Qt::ControlModifier | Qt::ShiftModifier);
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacCut() {
|
||||
_sendKeySequence(Qt::Key_X, Qt::ControlModifier);
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacCopy() {
|
||||
_sendKeySequence(Qt::Key_C, Qt::ControlModifier);
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacPaste() {
|
||||
_sendKeySequence(Qt::Key_V, Qt::ControlModifier);
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacDelete() {
|
||||
_sendKeySequence(Qt::Key_Delete);
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacSelectAll() {
|
||||
_sendKeySequence(Qt::Key_A, Qt::ControlModifier);
|
||||
}
|
||||
|
||||
bool PsMainWindow::psHandleTitle() {
|
||||
@@ -350,6 +436,40 @@ void PsMainWindow::psUpdateSysMenu(Qt::WindowState state) {
|
||||
void PsMainWindow::psUpdateMargins() {
|
||||
}
|
||||
|
||||
void PsMainWindow::psMacUpdateMenu() {
|
||||
if (!posInited) return;
|
||||
|
||||
QWidget *focused = QApplication::focusWidget();
|
||||
bool isLogged = !!App::self(), canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false;
|
||||
if (QLineEdit *edit = qobject_cast<QLineEdit*>(focused)) {
|
||||
canCut = canCopy = canDelete = edit->hasSelectedText();
|
||||
canSelectAll = !edit->text().isEmpty();
|
||||
canUndo = edit->isUndoAvailable();
|
||||
canRedo = edit->isRedoAvailable();
|
||||
canPaste = !App::app()->clipboard()->text().isEmpty();
|
||||
} else if (FlatTextarea *edit = qobject_cast<FlatTextarea*>(focused)) {
|
||||
canCut = canCopy = canDelete = edit->textCursor().hasSelection();
|
||||
canSelectAll = edit->hasText();
|
||||
canUndo = edit->isUndoAvailable();
|
||||
canRedo = edit->isRedoAvailable();
|
||||
canPaste = !App::app()->clipboard()->text().isEmpty();
|
||||
} else if (HistoryList *list = qobject_cast<HistoryList*>(focused)) {
|
||||
canCopy = list->canCopySelected();
|
||||
canDelete = list->canDeleteSelected();
|
||||
}
|
||||
_forceDisabled(psLogout, !isLogged);
|
||||
_forceDisabled(psUndo, !canUndo);
|
||||
_forceDisabled(psRedo, !canRedo);
|
||||
_forceDisabled(psCut, !canCut);
|
||||
_forceDisabled(psCopy, !canCopy);
|
||||
_forceDisabled(psPaste, !canPaste);
|
||||
_forceDisabled(psDelete, !canDelete);
|
||||
_forceDisabled(psSelectAll, !canSelectAll);
|
||||
_forceDisabled(psContacts, !isLogged);
|
||||
_forceDisabled(psNewGroup, !isLogged);
|
||||
_forceDisabled(psShowTelegram, psIsActive());
|
||||
}
|
||||
|
||||
void PsMainWindow::psFlash() {
|
||||
_private.startBounce();
|
||||
}
|
||||
@@ -402,6 +522,16 @@ void PsMainWindow::psPlatformNotify(HistoryItem *item) {
|
||||
_private.showNotify(item->history()->peer->id, title, subtitle, msg, (cNotifyView() <= dbinvShowPreview));
|
||||
}
|
||||
|
||||
bool PsMainWindow::eventFilter(QObject *obj, QEvent *evt) {
|
||||
QEvent::Type t = evt->type();
|
||||
if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
|
||||
if (qobject_cast<QLineEdit*>(obj) || qobject_cast<FlatTextarea*>(obj) || qobject_cast<HistoryList*>(obj)) {
|
||||
psMacUpdateMenu();
|
||||
}
|
||||
}
|
||||
return QMainWindow::eventFilter(obj, evt);
|
||||
}
|
||||
|
||||
PsApplication::PsApplication(int &argc, char **argv) : QApplication(argc, argv) {
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class MacPrivate : public PsMacWindowPrivate {
|
||||
public:
|
||||
|
||||
void activeSpaceChanged();
|
||||
void darkModeChanged();
|
||||
void notifyClicked(unsigned long long peer);
|
||||
void notifyReplied(unsigned long long peer, const char *str);
|
||||
|
||||
@@ -79,6 +80,8 @@ public:
|
||||
void psNotifyShown(NotifyWindow *w);
|
||||
void psPlatformNotify(HistoryItem *item);
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *evt);
|
||||
|
||||
~PsMainWindow();
|
||||
|
||||
public slots:
|
||||
@@ -90,15 +93,26 @@ public slots:
|
||||
void psIdleTimeout();
|
||||
void psShowTrayMenu();
|
||||
|
||||
void psMacUndo();
|
||||
void psMacRedo();
|
||||
void psMacCut();
|
||||
void psMacCopy();
|
||||
void psMacPaste();
|
||||
void psMacDelete();
|
||||
void psMacSelectAll();
|
||||
|
||||
protected:
|
||||
|
||||
void psNotIdle() const;
|
||||
QImage psTrayIcon(bool selected = false) const;
|
||||
|
||||
void psMacUpdateMenu();
|
||||
|
||||
bool posInited;
|
||||
QSystemTrayIcon *trayIcon;
|
||||
QMenu *trayIconMenu;
|
||||
QImage icon256;
|
||||
QIcon wndIcon;
|
||||
|
||||
QImage trayImg, trayImgSel;
|
||||
|
||||
@@ -113,6 +127,10 @@ private:
|
||||
|
||||
mutable bool psIdle;
|
||||
mutable QTimer psIdleTimer;
|
||||
|
||||
QMenuBar psMainMenu;
|
||||
QAction *psLogout, *psUndo, *psRedo, *psCut, *psCopy, *psPaste, *psDelete, *psSelectAll, *psContacts, *psNewGroup, *psShowTelegram;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
|
||||
virtual void activeSpaceChanged() {
|
||||
}
|
||||
virtual void darkModeChanged() {
|
||||
}
|
||||
virtual void notifyClicked(unsigned long long peer) {
|
||||
}
|
||||
virtual void notifyReplied(unsigned long long peer, const char *str) {
|
||||
@@ -48,6 +50,7 @@ public:
|
||||
};
|
||||
|
||||
void objc_holdOnTop(WId winId);
|
||||
bool objc_darkMode();
|
||||
void objc_showOverAll(WId winId, bool canFocus = true);
|
||||
void objc_bringToBack(WId winId);
|
||||
void objc_activateWnd(WId winId);
|
||||
|
||||
@@ -85,6 +85,7 @@ QNSString objc_lang(LangKey key) {
|
||||
|
||||
- (id) init:(PsMacWindowPrivate *)aWnd;
|
||||
- (void) activeSpaceDidChange:(NSNotification *)aNotification;
|
||||
- (void) darkModeChanged:(NSNotification *)aNotification;
|
||||
|
||||
@end
|
||||
|
||||
@@ -145,6 +146,10 @@ public:
|
||||
wnd->activeSpaceChanged();
|
||||
}
|
||||
|
||||
- (void) darkModeChanged:(NSNotification *)aNotification {
|
||||
wnd->darkModeChanged();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation NotifyHandler {
|
||||
@@ -180,6 +185,7 @@ public:
|
||||
|
||||
PsMacWindowPrivate::PsMacWindowPrivate() : data(new PsMacWindowData(this)) {
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:data->observerHelper selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil];
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:data->observerHelper selector:@selector(darkModeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];
|
||||
}
|
||||
|
||||
void PsMacWindowPrivate::setWindowBadge(const QString &str) {
|
||||
@@ -200,6 +206,13 @@ void objc_holdOnTop(WId winId) {
|
||||
[wnd setHidesOnDeactivate:NO];
|
||||
}
|
||||
|
||||
bool objc_darkMode() {
|
||||
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
|
||||
id style = [dict objectForKey:@"AppleInterfaceStyle"];
|
||||
BOOL darkModeOn = ( style && [style isKindOfClass:[NSString class]] && NSOrderedSame == [style caseInsensitiveCompare:@"dark"] );
|
||||
return darkModeOn ? true : false;
|
||||
}
|
||||
|
||||
void objc_showOverAll(WId winId, bool canFocus) {
|
||||
NSWindow *wnd = [reinterpret_cast<NSView *>(winId) window];
|
||||
[wnd setLevel:NSPopUpMenuWindowLevel];
|
||||
|
||||
@@ -860,7 +860,7 @@ namespace {
|
||||
|
||||
};
|
||||
|
||||
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), ps_hWnd(0), ps_menu(0), icon256(qsl(":/gui/art/iconround256.png")),
|
||||
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent), ps_hWnd(0), ps_menu(0), icon256(qsl(":/gui/art/icon256.png")), wndIcon(QPixmap::fromImage(icon256)),
|
||||
ps_iconBig(0), ps_iconSmall(0), ps_iconOverlay(0), trayIcon(0), trayIconMenu(0), posInited(false), ps_tbHider_hWnd(createTaskbarHider()), psIdle(false) {
|
||||
tbCreatedMsgId = RegisterWindowMessage(L"TaskbarButtonCreated");
|
||||
connect(&psIdleTimer, SIGNAL(timeout()), this, SLOT(psIdleTimeout()));
|
||||
|
||||
@@ -87,6 +87,7 @@ protected:
|
||||
QSystemTrayIcon *trayIcon;
|
||||
ContextMenu *trayIconMenu;
|
||||
QImage icon256;
|
||||
QIcon wndIcon;
|
||||
|
||||
virtual void setupTrayIcon() = 0;
|
||||
virtual QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon) = 0;
|
||||
|
||||
@@ -137,47 +137,59 @@ const RecentEmojiPack &cGetRecentEmojis() {
|
||||
cSetRecentEmojisPreload(RecentEmojiPreload());
|
||||
r.reserve(p.size());
|
||||
for (RecentEmojiPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) {
|
||||
EmojiPtr ep(getEmoji(i->first));
|
||||
if (ep) {
|
||||
r.push_back(qMakePair(ep, i->second));
|
||||
uint32 code = ((i->first & 0xFFFFU) == 0xFE0FU) ? ((i->first >> 16) & 0xFFFFU) : i->first;
|
||||
EmojiPtr ep(getEmoji(code));
|
||||
if (!ep) continue;
|
||||
|
||||
if (ep->postfix) {
|
||||
int32 j = 0, l = r.size();
|
||||
for (; j < l; ++j) {
|
||||
if (r[j].first->code == code) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < l) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
r.push_back(qMakePair(ep, i->second));
|
||||
}
|
||||
}
|
||||
uint32 defaultRecent[] = {
|
||||
0xD83DDE02,
|
||||
0xD83DDE18,
|
||||
0x2764,
|
||||
0xD83DDE0D,
|
||||
0xD83DDE0A,
|
||||
0xD83DDE01,
|
||||
0xD83DDC4D,
|
||||
0x263A,
|
||||
0xD83DDE14,
|
||||
0xD83DDE04,
|
||||
0xD83DDE2D,
|
||||
0xD83DDC8B,
|
||||
0xD83DDE12,
|
||||
0xD83DDE33,
|
||||
0xD83DDE1C,
|
||||
0xD83DDE48,
|
||||
0xD83DDE09,
|
||||
0xD83DDE03,
|
||||
0xD83DDE22,
|
||||
0xD83DDE1D,
|
||||
0xD83DDE31,
|
||||
0xD83DDE21,
|
||||
0xD83DDE0F,
|
||||
0xD83DDE1E,
|
||||
0xD83DDE05,
|
||||
0xD83DDE1A,
|
||||
0xD83DDE4A,
|
||||
0xD83DDE0C,
|
||||
0xD83DDE00,
|
||||
0xD83DDE0B,
|
||||
0xD83DDE06,
|
||||
0xD83DDC4C,
|
||||
0xD83DDE10,
|
||||
0xD83DDE15,
|
||||
0xD83DDE02U,
|
||||
0xD83DDE18U,
|
||||
0x2764U,
|
||||
0xD83DDE0DU,
|
||||
0xD83DDE0AU,
|
||||
0xD83DDE01U,
|
||||
0xD83DDC4DU,
|
||||
0x263AU,
|
||||
0xD83DDE14U,
|
||||
0xD83DDE04U,
|
||||
0xD83DDE2DU,
|
||||
0xD83DDC8BU,
|
||||
0xD83DDE12U,
|
||||
0xD83DDE33U,
|
||||
0xD83DDE1CU,
|
||||
0xD83DDE48U,
|
||||
0xD83DDE09U,
|
||||
0xD83DDE03U,
|
||||
0xD83DDE22U,
|
||||
0xD83DDE1DU,
|
||||
0xD83DDE31U,
|
||||
0xD83DDE21U,
|
||||
0xD83DDE0FU,
|
||||
0xD83DDE1EU,
|
||||
0xD83DDE05U,
|
||||
0xD83DDE1AU,
|
||||
0xD83DDE4AU,
|
||||
0xD83DDE0CU,
|
||||
0xD83DDE00U,
|
||||
0xD83DDE0BU,
|
||||
0xD83DDE06U,
|
||||
0xD83DDC4CU,
|
||||
0xD83DDE10U,
|
||||
0xD83DDE15U,
|
||||
};
|
||||
for (int32 i = 0, s = sizeof(defaultRecent) / sizeof(defaultRecent[0]); i < s; ++i) {
|
||||
if (r.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) break;
|
||||
|
||||
@@ -124,11 +124,12 @@ T convertScale(T v) {
|
||||
DeclareSetting(DBIEmojiTab, EmojiTab);
|
||||
|
||||
struct EmojiData {
|
||||
EmojiData(int32 x, int32 y, uint32 code, uint32 code2, int32 len) : x(x), y(y), code(code), code2(code2), len(len) {
|
||||
EmojiData(uint16 x, uint16 y, uint32 code, uint32 code2, uint16 len, uint16 postfix = 0) : x(x), y(y), code(code), code2(code2), len(len), postfix(postfix) {
|
||||
}
|
||||
int32 x, y;
|
||||
uint16 x, y;
|
||||
uint32 code, code2;
|
||||
int32 len;
|
||||
uint16 len;
|
||||
uint16 postfix;
|
||||
};
|
||||
|
||||
typedef const EmojiData *EmojiPtr;
|
||||
|
||||
@@ -31,6 +31,8 @@ Copyright (c) 2014 John Preston, https://tdesktop.com
|
||||
#include "boxes/usernamebox.h"
|
||||
#include "gui/filedialog.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
Slider::Slider(QWidget *parent, const style::slider &st, int32 count, int32 sel) : QWidget(parent),
|
||||
_count(count), _sel(snap(sel, 0, _count)), _wasSel(_sel), _st(st), _pressed(false) {
|
||||
resize(_st.width, _st.bar.pxHeight());
|
||||
@@ -154,6 +156,12 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent),
|
||||
|
||||
_catsAndDogs(this, lang(lng_settings_cats_and_dogs), cCatsAndDogs()),
|
||||
|
||||
// local storage
|
||||
_localImagesClear(this, lang(lng_local_images_clear)),
|
||||
_imagesClearingWidth(st::linkFont->m.width(lang(lng_local_images_clearing))),
|
||||
_imagesClearedWidth(st::linkFont->m.width(lang(lng_local_images_cleared))),
|
||||
_imagesClearFailedWidth(st::linkFont->m.width(lang(lng_local_images_clear_failed))),
|
||||
|
||||
// advanced
|
||||
_connectionType(this, lang(lng_connection_auto)),
|
||||
_resetSessions(this, lang(lng_settings_reset)),
|
||||
@@ -231,15 +239,23 @@ SettingsInner::SettingsInner(SettingsWidget *parent) : QWidget(parent),
|
||||
case Window::TempDirExists: _tempDirClearState = TempDirExists; break;
|
||||
case Window::TempDirRemoving: _tempDirClearState = TempDirClearing; break;
|
||||
}
|
||||
connect(App::wnd(), SIGNAL(tempDirCleared()), this, SLOT(onTempDirCleared()));
|
||||
connect(App::wnd(), SIGNAL(tempDirClearFailed()), this, SLOT(onTempDirClearFailed()));
|
||||
connect(App::wnd(), SIGNAL(tempDirCleared(int)), this, SLOT(onTempDirCleared(int)));
|
||||
connect(App::wnd(), SIGNAL(tempDirClearFailed(int)), this, SLOT(onTempDirClearFailed(int)));
|
||||
|
||||
connect(&_catsAndDogs, SIGNAL(changed()), this, SLOT(onCatsAndDogs()));
|
||||
|
||||
// local storage
|
||||
connect(&_localImagesClear, SIGNAL(clicked()), this, SLOT(onLocalImagesClear()));
|
||||
switch (App::wnd()->localImagesState()) {
|
||||
case Window::TempDirEmpty: _imagesClearState = TempDirEmpty; break;
|
||||
case Window::TempDirExists: _imagesClearState = TempDirExists; break;
|
||||
case Window::TempDirRemoving: _imagesClearState = TempDirClearing; break;
|
||||
}
|
||||
|
||||
// advanced
|
||||
connect(&_connectionType, SIGNAL(clicked()), this, SLOT(onConnectionType()));
|
||||
connect(&_resetSessions, SIGNAL(clicked()), this, SLOT(onResetSessions()));
|
||||
connect(&_logOut, SIGNAL(clicked()), this, SLOT(onLogout()));
|
||||
connect(&_logOut, SIGNAL(clicked()), App::wnd(), SLOT(onLogout()));
|
||||
|
||||
_connectionTypeText = lang(lng_connection_type) + ' ';
|
||||
_connectionTypeWidth = st::linkFont->m.width(_connectionTypeText);
|
||||
@@ -456,14 +472,40 @@ void SettingsInner::paintEvent(QPaintEvent *e) {
|
||||
top += st::setSectionSkip;
|
||||
|
||||
top += _catsAndDogs.height();
|
||||
|
||||
// local storage
|
||||
p.setFont(st::setHeaderFont->f);
|
||||
p.setPen(st::setHeaderColor->p);
|
||||
p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_cache));
|
||||
top += st::setHeaderSkip;
|
||||
|
||||
QString localImagesText = lang(lng_settings_no_images_cached);
|
||||
int32 cnt = Local::hasImages();
|
||||
if (cnt) {
|
||||
localImagesText = lang((cnt > 1) ? lng_settings_images_cached : lng_settings_image_cached).replace(qsl("{count}"), QString::number(cnt)).replace(qsl("{size}"), formatSizeText(Local::storageFilesSize()));
|
||||
}
|
||||
p.setFont(st::linkFont->f);
|
||||
p.setPen(st::black->p);
|
||||
p.drawText(_left + st::setHeaderLeft, top + st::linkFont->ascent, localImagesText);
|
||||
QString clearText;
|
||||
int32 clearWidth = 0;
|
||||
switch (_imagesClearState) {
|
||||
case TempDirClearing: clearText = lang(lng_local_images_clearing); clearWidth = _imagesClearingWidth; break;
|
||||
case TempDirCleared: clearText = lang(lng_local_images_cleared); clearWidth = _imagesClearedWidth; break;
|
||||
case TempDirClearFailed: clearText = lang(lng_local_images_clear_failed); clearWidth = _imagesClearFailedWidth; break;
|
||||
}
|
||||
if (clearWidth) {
|
||||
p.drawText(_left + st::setWidth - clearWidth, top + st::linkFont->ascent, clearText);
|
||||
}
|
||||
top += _localImagesClear.height();
|
||||
}
|
||||
|
||||
|
||||
// advanced
|
||||
p.setFont(st::setHeaderFont->f);
|
||||
p.setPen(st::setHeaderColor->p);
|
||||
p.drawText(_left + st::setHeaderLeft, top + st::setHeaderTop + st::setHeaderFont->ascent, lang(lng_settings_section_advanced));
|
||||
top += st::setHeaderSkip;
|
||||
|
||||
|
||||
p.setFont(st::linkFont->f);
|
||||
p.setPen(st::black->p);
|
||||
p.drawText(_left + st::setHeaderLeft, _connectionType.y() + st::linkFont->ascent, _connectionTypeText);
|
||||
@@ -541,6 +583,10 @@ void SettingsInner::resizeEvent(QResizeEvent *e) {
|
||||
}
|
||||
top += st::setSectionSkip;
|
||||
_catsAndDogs.move(_left, top); top += _catsAndDogs.height();
|
||||
|
||||
// local storage
|
||||
top += st::setHeaderSkip;
|
||||
_localImagesClear.move(_left + st::setWidth - _localImagesClear.width(), top); top += _localImagesClear.height();
|
||||
}
|
||||
|
||||
// advanced
|
||||
@@ -754,6 +800,7 @@ void SettingsInner::showAll() {
|
||||
_downloadPathClear.hide();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
_replaceEmojis.hide();
|
||||
_viewEmojis.hide();
|
||||
@@ -763,6 +810,14 @@ void SettingsInner::showAll() {
|
||||
_dontAskDownloadPath.hide();
|
||||
_downloadPathEdit.hide();
|
||||
_downloadPathClear.hide();
|
||||
_localImagesClear.hide();
|
||||
}
|
||||
|
||||
// local storage
|
||||
if (self() && _imagesClearState == TempDirExists) {
|
||||
_localImagesClear.show();
|
||||
} else {
|
||||
_localImagesClear.hide();
|
||||
}
|
||||
|
||||
// advanced
|
||||
@@ -823,10 +878,6 @@ void SettingsInner::onUpdatePhoto() {
|
||||
App::wnd()->showLayer(box);
|
||||
}
|
||||
|
||||
void SettingsInner::onLogout() {
|
||||
App::logOut();
|
||||
}
|
||||
|
||||
void SettingsInner::onResetSessions() {
|
||||
MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&SettingsInner::doneResetSessions));
|
||||
}
|
||||
@@ -1107,20 +1158,35 @@ void SettingsInner::onDownloadPathClear() {
|
||||
|
||||
void SettingsInner::onDownloadPathClearSure() {
|
||||
App::wnd()->hideLayer();
|
||||
App::wnd()->tempDirDelete();
|
||||
App::wnd()->tempDirDelete(Local::ClearManagerDownloads);
|
||||
_tempDirClearState = TempDirClearing;
|
||||
showAll();
|
||||
update();
|
||||
}
|
||||
|
||||
void SettingsInner::onTempDirCleared() {
|
||||
_tempDirClearState = TempDirCleared;
|
||||
void SettingsInner::onLocalImagesClear() {
|
||||
App::wnd()->tempDirDelete(Local::ClearManagerImages);
|
||||
_imagesClearState = TempDirClearing;
|
||||
showAll();
|
||||
update();
|
||||
}
|
||||
|
||||
void SettingsInner::onTempDirClearFailed() {
|
||||
_tempDirClearState = TempDirClearFailed;
|
||||
void SettingsInner::onTempDirCleared(int task) {
|
||||
if (task & Local::ClearManagerDownloads) {
|
||||
_tempDirClearState = TempDirCleared;
|
||||
} else if (task & Local::ClearManagerImages) {
|
||||
_imagesClearState = TempDirCleared;
|
||||
}
|
||||
showAll();
|
||||
update();
|
||||
}
|
||||
|
||||
void SettingsInner::onTempDirClearFailed(int task) {
|
||||
if (task & Local::ClearManagerDownloads) {
|
||||
_tempDirClearState = TempDirClearFailed;
|
||||
} else if (task & Local::ClearManagerImages) {
|
||||
_imagesClearState = TempDirClearFailed;
|
||||
}
|
||||
showAll();
|
||||
update();
|
||||
}
|
||||
|
||||