diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index fe6d3752c..4eaf2ec22 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -52,7 +52,7 @@ jobs:
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
env:
- UPLOAD_ARTIFACT: "false"
+ UPLOAD_ARTIFACT: "true"
steps:
- name: Get repository name.
@@ -115,8 +115,8 @@ jobs:
if: env.UPLOAD_ARTIFACT == 'true'
run: |
cd $REPO_NAME/out/Debug
- mkdir artifact
- mv {Telegram,Updater} artifact/
+ sudo mkdir artifact
+ sudo mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml
index 61561be9f..8271664c0 100644
--- a/.github/workflows/mac.yml
+++ b/.github/workflows/mac.yml
@@ -47,7 +47,7 @@ jobs:
defines:
- ""
env:
- UPLOAD_ARTIFACT: "false"
+ UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false"
PREPARE_PATH: "Telegram/build/prepare/prepare.py"
diff --git a/.github/workflows/mac_packaged.yml b/.github/workflows/mac_packaged.yml
index 51ba9917a..8ed4ffca1 100644
--- a/.github/workflows/mac_packaged.yml
+++ b/.github/workflows/mac_packaged.yml
@@ -50,7 +50,7 @@ jobs:
env:
GIT: "https://github.com"
OPENALDIR: "/usr/local/opt/openal-soft"
- UPLOAD_ARTIFACT: "false"
+ UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false"
MANUAL_CACHING: "1"
AUTO_CACHING: "1"
diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml
index c2e5dbafe..0c2bc4148 100644
--- a/.github/workflows/snap.yml
+++ b/.github/workflows/snap.yml
@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-20.04
env:
- UPLOAD_ARTIFACT: "false"
+ UPLOAD_ARTIFACT: "true"
steps:
- name: Clone.
diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml
index b42906c7c..f8332c36f 100644
--- a/.github/workflows/win.yml
+++ b/.github/workflows/win.yml
@@ -50,7 +50,7 @@ jobs:
generator: ["", "Ninja Multi-Config"]
env:
- UPLOAD_ARTIFACT: "false"
+ UPLOAD_ARTIFACT: "true"
ONLY_CACHE: "false"
PREPARE_PATH: "Telegram/build/prepare/prepare.py"
diff --git a/.gitmodules b/.gitmodules
index b489d4b7e..64928f01b 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -76,6 +76,9 @@
[submodule "Telegram/lib_webview"]
path = Telegram/lib_webview
url = https://github.com/desktop-app/lib_webview.git
+[submodule "Telegram/ThirdParty/jemalloc"]
+ path = Telegram/ThirdParty/jemalloc
+ url = https://github.com/jemalloc/jemalloc
[submodule "Telegram/ThirdParty/dispatch"]
path = Telegram/ThirdParty/dispatch
url = https://github.com/apple/swift-corelibs-libdispatch
diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index ae861a65a..4db06b26f 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -1574,6 +1574,8 @@ PRIVATE
window/window_lock_widgets.h
window/window_main_menu.cpp
window/window_main_menu.h
+ window/window_main_menu_helpers.cpp
+ window/window_main_menu_helpers.h
window/window_media_preview.cpp
window/window_media_preview.h
window/window_peer_menu.cpp
diff --git a/Telegram/Resources/animations/collectible_phone.tgs b/Telegram/Resources/animations/collectible_phone.tgs
new file mode 100644
index 000000000..4e7284d5f
Binary files /dev/null and b/Telegram/Resources/animations/collectible_phone.tgs differ
diff --git a/Telegram/Resources/animations/collectible_username.tgs b/Telegram/Resources/animations/collectible_username.tgs
new file mode 100644
index 000000000..f93fd6d74
Binary files /dev/null and b/Telegram/Resources/animations/collectible_username.tgs differ
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 77254d7a2..00e0bcf7c 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -19,6 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_set_status" = "Set Emoji Status";
"lng_menu_change_status" = "Change Emoji Status";
"lng_menu_my_stories" = "My Stories";
+"lng_menu_my_groups" = "My Groups";
+"lng_menu_my_channels" = "My Channels";
"lng_disable_notifications_from_tray" = "Disable notifications";
"lng_enable_notifications_from_tray" = "Enable notifications";
@@ -469,6 +471,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_bio_placeholder" = "Bio";
+"lng_collectible_username_title" = "{username} is a collectible username that belongs to";
+"lng_collectible_username_info" = "This username was bought on **Fragment** on {date} for {price}";
+"lng_collectible_username_copy" = "Copy Link";
+"lng_collectible_phone_title" = "{phone} is a collectible phone number that belongs to";
+"lng_collectible_phone_info" = "This phone number was bought on **Fragment** on {date} for {price}";
+"lng_collectible_phone_copy" = "Copy Phone Number";
+"lng_collectible_learn_more" = "Learn More";
+
"lng_settings_section_info" = "My info";
"lng_settings_section_notify" = "Notifications";
@@ -1058,6 +1068,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_faq_button" = "Go to FAQ";
"lng_settings_ask_ok" = "Ask a Volunteer";
"lng_settings_faq" = "Telegram FAQ";
+"lng_settings_faq_link" = "https://telegram.org/faq#general-questions";
"lng_settings_features" = "Telegram Features";
"lng_settings_logout" = "Log Out";
"lng_sure_logout" = "Are you sure you want to log out?";
@@ -5040,6 +5051,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channel_earn_learn_coin_title" = "What is {emoji} TON?";
"lng_channel_earn_learn_coin_about" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its high speed and low commissions on transactions. {link}";
"lng_channel_earn_learn_close" = "Got it";
+"lng_channel_earn_learn_coin_link" = "https://telegram.org/blog/monetization-for-channels";
"lng_channel_earn_chart_top_hours" = "Ad impressions";
"lng_channel_earn_chart_revenue" = "Ad revenue";
"lng_channel_earn_chart_overriden_detail_currency" = "Revenue in TON";
diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc
index b63b6d15f..5b72c461f 100644
--- a/Telegram/Resources/qrc/telegram/animations.qrc
+++ b/Telegram/Resources/qrc/telegram/animations.qrc
@@ -22,5 +22,7 @@
../../animations/hours.tgs
../../animations/phone.tgs
../../animations/chat_link.tgs
+ ../../animations/collectible_username.tgs
+ ../../animations/collectible_phone.tgs
diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml
index 2627ef790..3488163e5 100644
--- a/Telegram/Resources/uwp/AppX/AppxManifest.xml
+++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml
@@ -10,7 +10,7 @@
+ Version="4.16.6.0" />
Telegram Desktop
Telegram Messenger LLP
diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc
index 7585dfef0..07be79162 100644
--- a/Telegram/Resources/winrc/Telegram.rc
+++ b/Telegram/Resources/winrc/Telegram.rc
@@ -44,8 +44,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,16,0,0
- PRODUCTVERSION 4,16,0,0
+ FILEVERSION 4,16,6,0
+ PRODUCTVERSION 4,16,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -62,10 +62,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop"
- VALUE "FileVersion", "4.16.0.0"
+ VALUE "FileVersion", "4.16.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.16.0.0"
+ VALUE "ProductVersion", "4.16.6.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc
index ae31782f3..88221f475 100644
--- a/Telegram/Resources/winrc/Updater.rc
+++ b/Telegram/Resources/winrc/Updater.rc
@@ -35,8 +35,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
- FILEVERSION 4,16,0,0
- PRODUCTVERSION 4,16,0,0
+ FILEVERSION 4,16,6,0
+ PRODUCTVERSION 4,16,6,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -53,10 +53,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Radolyn Labs"
VALUE "FileDescription", "AyuGram Desktop Updater"
- VALUE "FileVersion", "4.16.0.0"
+ VALUE "FileVersion", "4.16.6.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2024"
VALUE "ProductName", "AyuGram Desktop"
- VALUE "ProductVersion", "4.16.0.0"
+ VALUE "ProductVersion", "4.16.6.0"
END
END
BLOCK "VarFileInfo"
diff --git a/Telegram/SourceFiles/api/api_peer_photo.cpp b/Telegram/SourceFiles/api/api_peer_photo.cpp
index c70ef4491..9c8a470fa 100644
--- a/Telegram/SourceFiles/api/api_peer_photo.cpp
+++ b/Telegram/SourceFiles/api/api_peer_photo.cpp
@@ -34,7 +34,7 @@ namespace {
constexpr auto kSharedMediaLimit = 100;
-[[nodiscard]] SendMediaReady PreparePeerPhoto(
+[[nodiscard]] std::shared_ptr PreparePeerPhoto(
MTP::DcId dcId,
PeerId peerId,
QImage &&image) {
@@ -80,24 +80,17 @@ constexpr auto kSharedMediaLimit = 100;
MTPVector(),
MTP_int(dcId));
- QString file, filename;
- int64 filesize = 0;
- QByteArray data;
-
- return SendMediaReady(
- SendMediaType::Photo,
- file,
- filename,
- filesize,
- data,
- id,
- id,
- u"jpg"_q,
- peerId,
- photo,
- photoThumbs,
- MTP_documentEmpty(MTP_long(0)),
- jpeg);
+ auto result = MakePreparedFile({
+ .id = id,
+ .type = SendMediaType::Photo,
+ });
+ result->type = SendMediaType::Photo;
+ result->setFileData(jpeg);
+ result->thumbId = id;
+ result->thumbname = "thumb.jpg";
+ result->photo = photo;
+ result->photoThumbs = photoThumbs;
+ return result;
}
[[nodiscard]] std::optional PrepareMtpMarkup(
@@ -239,7 +232,7 @@ void PeerPhoto::upload(
_api.instance().mainDcId(),
peer->id,
base::take(photo.image));
- _session->uploader().uploadMedia(fakeId, ready);
+ _session->uploader().upload(fakeId, ready);
}
}
diff --git a/Telegram/SourceFiles/api/api_ringtones.cpp b/Telegram/SourceFiles/api/api_ringtones.cpp
index 87522bf58..307ba580a 100644
--- a/Telegram/SourceFiles/api/api_ringtones.cpp
+++ b/Telegram/SourceFiles/api/api_ringtones.cpp
@@ -24,16 +24,25 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Api {
namespace {
-SendMediaReady PrepareRingtoneDocument(
+std::shared_ptr PrepareRingtoneDocument(
MTP::DcId dcId,
const QString &filename,
const QString &filemime,
const QByteArray &content) {
+ const auto id = base::RandomValue();
auto attributes = QVector(
1,
MTP_documentAttributeFilename(MTP_string(filename)));
- const auto id = base::RandomValue();
- const auto document = MTP_document(
+
+ auto result = MakePreparedFile({
+ .id = id,
+ .type = SendMediaType::File,
+ });
+ result->filename = filename;
+ result->content = content;
+ result->filesize = content.size();
+ result->setFileData(content);
+ result->document = MTP_document(
MTP_flags(0),
MTP_long(id),
MTP_long(0),
@@ -45,21 +54,7 @@ SendMediaReady PrepareRingtoneDocument(
MTPVector(),
MTP_int(dcId),
MTP_vector(std::move(attributes)));
-
- return SendMediaReady(
- SendMediaType::File,
- QString(), // filepath
- filename,
- content.size(),
- content,
- id,
- 0,
- QString(),
- PeerId(),
- MTP_photoEmpty(MTP_long(0)),
- PreparedPhotoThumbs(),
- document,
- QByteArray());
+ return result;
}
} // namespace
@@ -102,7 +97,7 @@ void Ringtones::upload(
_uploads.erase(already);
}
_uploads.emplace(fakeId, uploadedData);
- _session->uploader().uploadMedia(fakeId, ready);
+ _session->uploader().upload(fakeId, ready);
}
void Ringtones::ready(const FullMsgId &msgId, const MTPInputFile &file) {
diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp
index 72bd06b3d..d91943fe0 100644
--- a/Telegram/SourceFiles/api/api_sending.cpp
+++ b/Telegram/SourceFiles/api/api_sending.cpp
@@ -353,7 +353,7 @@ void FillMessagePostFlags(
void SendConfirmedFile(
not_null session,
- const std::shared_ptr &file) {
+ const std::shared_ptr &file) {
const auto isEditing = (file->type != SendMediaType::Audio)
&& (file->to.replaceMediaOf != 0);
const auto newId = FullMsgId(
diff --git a/Telegram/SourceFiles/api/api_sending.h b/Telegram/SourceFiles/api/api_sending.h
index e17c66f3e..2fdbad843 100644
--- a/Telegram/SourceFiles/api/api_sending.h
+++ b/Telegram/SourceFiles/api/api_sending.h
@@ -14,7 +14,7 @@ class Session;
class History;
class PhotoData;
class DocumentData;
-struct FileLoadResult;
+struct FilePrepareResult;
namespace Api {
@@ -40,6 +40,6 @@ void FillMessagePostFlags(
void SendConfirmedFile(
not_null session,
- const std::shared_ptr &file);
+ const std::shared_ptr &file);
} // namespace Api
diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp
index 794088ed4..ab030fb57 100644
--- a/Telegram/SourceFiles/apiwrap.cpp
+++ b/Telegram/SourceFiles/apiwrap.cpp
@@ -813,7 +813,7 @@ QString ApiWrap::exportDirectStoryLink(not_null story) {
const auto storyId = story->fullId();
const auto peer = story->peer();
const auto fallback = [&] {
- const auto base = peer->userName();
+ const auto base = peer->username();
const auto story = QString::number(storyId.story);
const auto query = base + "/s/" + story;
return session().createInternalLinkFull(query);
diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp
index 3a9917719..a112c478a 100644
--- a/Telegram/SourceFiles/boxes/background_preview_box.cpp
+++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp
@@ -593,11 +593,11 @@ void BackgroundPreviewBox::uploadForPeer(bool both) {
const auto ready = Window::Theme::PrepareWallPaper(
session->mainDcId(),
_paper.localThumbnail()->original());
- const auto documentId = ready.id;
+ const auto documentId = ready->id;
_uploadId = FullMsgId(
session->userPeerId(),
session->data().nextLocalMessageId());
- session->uploader().uploadMedia(_uploadId, ready);
+ session->uploader().upload(_uploadId, ready);
if (_uploadLifetime) {
return;
}
diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style
index f70c7b88d..c181168d1 100644
--- a/Telegram/SourceFiles/boxes/boxes.style
+++ b/Telegram/SourceFiles/boxes/boxes.style
@@ -1044,3 +1044,33 @@ inviteForbiddenTitle: FlatLabel(boxTitle) {
inviteForbiddenTitlePadding: margins(32px, 4px, 32px, 0px);
inviteForbiddenLockBg: dialogsUnreadBgMuted;
inviteForbiddenLockIcon: icon {{ "emoji/premium_lock", dialogsUnreadFg }};
+
+collectibleIconDiameter: 72px;
+collectibleIcon: 64px;
+collectibleIconPadding: margins(24px, 32px, 24px, 12px);
+collectibleHeader: FlatLabel(boxTitle) {
+ minWidth: 120px;
+ maxHeight: 0px;
+ align: align(top);
+}
+collectibleHeaderPadding: margins(24px, 16px, 24px, 12px);
+collectibleOwnerPadding: margins(24px, 4px, 24px, 8px);
+collectibleInfo: inviteForbiddenInfo;
+collectibleInfoPadding: margins(24px, 12px, 24px, 12px);
+collectibleInfoTonMargins: margins(0px, 3px, 0px, 0px);
+collectibleMore: RoundButton(defaultActiveButton) {
+ height: 36px;
+ textTop: 9px;
+ radius: 6px;
+}
+collectibleMorePadding: margins(24px, 12px, 24px, 0px);
+collectibleCopy: RoundButton(defaultLightButton) {
+ height: 36px;
+ textTop: 9px;
+ radius: 6px;
+}
+collectibleBox: Box(defaultBox) {
+ buttonPadding: margins(24px, 12px, 24px, 12px);
+ buttonHeight: 36px;
+ button: collectibleCopy;
+}
diff --git a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
index 9fcb6b032..7800e277e 100644
--- a/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
+++ b/Telegram/SourceFiles/boxes/edit_privacy_box.cpp
@@ -159,10 +159,7 @@ public:
-> rpl::producer;
private:
- [[nodiscard]] std::unique_ptr createRow() const;
-
const not_null _session;
- bool _premiums = false;
rpl::event_stream<> _selectionChanged;
rpl::event_stream _rowSelectionChanges;
@@ -209,8 +206,7 @@ bool PremiumsRow::useForumLikeUserpic() const {
TypesController::TypesController(
not_null session,
bool premiums)
-: _session(session)
-, _premiums(premiums) {
+: _session(session) {
}
Main::Session &TypesController::session() const {
@@ -331,6 +327,9 @@ auto PrivacyExceptionsBoxController::preparePremiumsRowList()
_deselectOption = [=](PeerListRowId itemId) {
if (const auto row = _typesDelegate->peerListFindRow(itemId)) {
+ if (itemId == kPremiumsRowId) {
+ _selected.premiums = false;
+ }
_typesDelegate->peerListSetRowChecked(row, false);
}
};
diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp
index 2430c583e..bda06d535 100644
--- a/Telegram/SourceFiles/boxes/peer_list_box.cpp
+++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp
@@ -1039,11 +1039,9 @@ void PeerListContent::changeCheckState(
not_null row,
bool checked,
anim::type animated) {
- row->setChecked(
- checked,
- _st.item.checkbox,
- animated,
- [=] { updateRow(row); });
+ row->setChecked(checked, _st.item.checkbox, animated, [=] {
+ updateRow(row);
+ });
}
void PeerListContent::setRowHidden(not_null row, bool hidden) {
@@ -1789,10 +1787,10 @@ crl::time PeerListContent::paintRow(
if (row->isSearchResult()
&& !_mentionHighlight.isEmpty()
&& peer
- && peer->userName().startsWith(
+ && peer->username().startsWith(
_mentionHighlight,
Qt::CaseInsensitive)) {
- const auto username = peer->userName();
+ const auto username = peer->username();
const auto availableWidth = statusw;
auto highlightedPart = '@' + username.mid(0, _mentionHighlight.size());
auto grayedPart = username.mid(_mentionHighlight.size());
diff --git a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
index f8a7ea6e8..bae3d8a76 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_linked_chat_box.cpp
@@ -100,7 +100,7 @@ void Controller::prepare() {
return;
}
auto row = std::make_unique(chat);
- const auto username = chat->userName();
+ const auto username = chat->username();
row->setCustomStatus(!username.isEmpty()
? ('@' + username)
: (chat->isChannel() && !chat->isMegagroup())
diff --git a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
index a6b442aca..6c19d4055 100644
--- a/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/prepare_short_info_box.cpp
@@ -207,7 +207,7 @@ void ProcessFullPhoto(
| UpdateFlag::Birthday)
) | rpl::map([=] {
const auto user = peer->asUser();
- const auto username = peer->userName();
+ const auto username = peer->username();
return PeerShortInfoFields{
.name = peer->name(),
.phone = user ? Ui::FormatPhone(user->phone()) : QString(),
diff --git a/Telegram/SourceFiles/boxes/premium_limits_box.cpp b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
index 2a9c0d4b1..451957c21 100644
--- a/Telegram/SourceFiles/boxes/premium_limits_box.cpp
+++ b/Telegram/SourceFiles/boxes/premium_limits_box.cpp
@@ -322,7 +322,7 @@ void PublicsController::prepare() {
auto &owner = _navigation->session().data();
for (const auto &chat : chats) {
if (const auto peer = owner.processChat(chat)) {
- if (!peer->isChannel() || peer->userName().isEmpty()) {
+ if (!peer->isChannel() || peer->username().isEmpty()) {
continue;
}
appendRow(peer);
@@ -346,7 +346,7 @@ void PublicsController::rowRightActionClicked(not_null row) {
const auto text = textMethod(
tr::now,
lt_link,
- peer->session().createInternalLink(peer->userName()),
+ peer->session().createInternalLink(peer->username()),
lt_group,
peer->name());
const auto confirmText = tr::lng_channels_too_much_public_revoke(
@@ -389,7 +389,7 @@ std::unique_ptr PublicsController::createRow(
auto result = std::make_unique(peer);
result->setActionLink(tr::lng_channels_too_much_public_revoke(tr::now));
result->setCustomStatus(
- _navigation->session().createInternalLink(peer->userName()));
+ _navigation->session().createInternalLink(peer->username()));
return result;
}
diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp
index 01f6a1ed1..0597a8fa2 100644
--- a/Telegram/SourceFiles/boxes/stickers_box.cpp
+++ b/Telegram/SourceFiles/boxes/stickers_box.cpp
@@ -652,8 +652,8 @@ void StickersBox::prepare() {
}
if (const auto featured = _featured.widget()) {
featured->setInstallSetCallback([=](uint64 setId) {
- markAsInstalledCallback(setId);
installCallback(setId);
+ markAsInstalledCallback(setId);
});
featured->setRemoveSetCallback(markAsRemovedCallback);
}
@@ -1760,8 +1760,11 @@ void StickersBox::Inner::setActionDown(int newActionDown) {
const auto &st = installedSet
? st::stickersTrendingInstalled
: st::stickersTrendingAdd;
+ const auto buttonTextWidth = installedSet
+ ? _installedWidth
+ : _addWidth;
auto rippleMask = Ui::RippleAnimation::RoundRectMask(
- QSize(_addWidth - st.width, st.height),
+ QSize(buttonTextWidth - st.width, st.height),
st::roundRadiusLarge);
ensureRipple(
st.ripple,
@@ -1902,9 +1905,19 @@ void StickersBox::Inner::updateSelected() {
selected = selectedIndex;
local.setY(local.y() - _itemsTop - selectedIndex * _rowHeight);
const auto row = _rows[selectedIndex].get();
- if (!_megagroupSet && (_isInstalledTab || (_section == Section::Featured) || !row->isInstalled() || row->isArchived() || row->removed)) {
+ if (!_megagroupSet
+ && (_isInstalledTab
+ || (_section == Section::Featured)
+ || !row->isInstalled()
+ || row->isArchived()
+ || row->removed)) {
auto removeButton = (_isInstalledTab && !row->removed);
- auto rect = myrtlrect(relativeButtonRect(removeButton, false));
+
+ const auto installedSetButton = !_isInstalledTab
+ && row->isInstalled()
+ && !row->isArchived()
+ && !row->removed;
+ auto rect = myrtlrect(relativeButtonRect(removeButton, installedSetButton));
actionSel = rect.contains(local) ? selectedIndex : -1;
} else {
actionSel = -1;
@@ -1957,12 +1970,19 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
_mouse = e->globalPos();
updateSelected();
- if (_actionDown == _actionSel && _actionSel >= 0) {
- const auto callback = _rows[_actionDown]->removed
- ? _installSetCallback
- : _removeSetCallback;
+ const auto down = _actionDown;
+ setActionDown(-1);
+ if (down == _actionSel && _actionSel >= 0) {
+ const auto row = _rows[down].get();
+ const auto installedSet = row->isInstalled()
+ && !row->isArchived()
+ && !row->removed;
+ const auto callback = installedSet
+ ? _removeSetCallback
+ : _installSetCallback;
if (callback) {
- callback(_rows[_actionDown]->set->id);
+ row->ripple.reset();
+ callback(row->set->id);
}
} else if (_dragging >= 0) {
_rows[_dragging]->yadd.start(0.);
@@ -1973,7 +1993,7 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
}
_dragging = _started = -1;
- } else if (pressed == _selected && _actionSel < 0 && _actionDown < 0) {
+ } else if (pressed == _selected && _actionSel < 0 && down < 0) {
const auto selectedIndex = [&] {
if (auto index = std::get_if(&_selected)) {
return *index;
@@ -1997,7 +2017,6 @@ void StickersBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
showSetByRow(*_megagroupSelectedSet);
}
}
- setActionDown(-1);
}
void StickersBox::Inner::saveGroupSet(Fn done) {
diff --git a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp
index a9f41b35a..02ae9cd68 100644
--- a/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp
+++ b/Telegram/SourceFiles/calls/group/calls_group_members_row.cpp
@@ -612,9 +612,9 @@ void MembersRow::paintComplexStatusText(
x += skip;
availableWidth -= skip;
const auto &font = st::normalFont;
- const auto useAbout = (style == MembersRowStyle::Video)
- ? false
- : ((_state == State::RaisedHand && !_raisedHandStatus)
+ const auto useAbout = !_about.isEmpty()
+ && (style != MembersRowStyle::Video)
+ && ((_state == State::RaisedHand && !_raisedHandStatus)
|| (_state != State::RaisedHand && !_speaking));
if (!useAbout
&& _state != State::Invited
diff --git a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
index ad59290c5..93c5ecf1e 100644
--- a/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
+++ b/Telegram/SourceFiles/calls/group/ui/desktop_capture_choose_source.cpp
@@ -587,11 +587,9 @@ void ChooseSourceProcess::setupGeometryWithParent(
not_null parent) {
_window->createWinId();
const auto parentScreen = [&] {
- if (!::Platform::IsWayland()) {
- if (const auto screen = QGuiApplication::screenAt(
+ if (const auto screen = QGuiApplication::screenAt(
parent->geometry().center())) {
- return screen;
- }
+ return screen;
}
return parent->screen();
}();
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
index caf790d27..ac8b6f117 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.cpp
@@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_document.h"
+#include "data/data_file_origin.h"
#include "data/data_peer_values.h"
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
@@ -51,10 +52,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_chat_helpers.h"
#include "styles/style_menu_icons.h"
-// AyuGram includes
-#include "data/data_file_origin.h"
-#include "qapplication.h"
-
+#include
namespace ChatHelpers {
namespace {
@@ -62,6 +60,7 @@ namespace {
constexpr auto kCollapsedRows = 3;
constexpr auto kAppearDuration = 0.3;
constexpr auto kCustomSearchLimit = 256;
+constexpr auto kColorPickerDelay = crl::time(500);
using Core::RecentEmojiId;
using Core::RecentEmojiDocument;
@@ -630,6 +629,15 @@ void EmojiListWidget::applyNextSearchQuery() {
}
}
+void EmojiListWidget::showPreview() {
+ if (const auto over = std::get_if(&_pressed)) {
+ if (const auto custom = lookupCustomEmoji(over)) {
+ _show->showMediaPreview(custom->stickerSetOrigin(), custom);
+ _previewShown = true;
+ }
+ }
+}
+
std::vector EmojiListWidget::collectPlainSearchResults() {
return SearchEmoji(_searchQuery, _searchEmoji);
}
@@ -1125,7 +1133,7 @@ void EmojiListWidget::fillRecentMenu(
const auto addAction = Ui::Menu::CreateAddActionCallback(menu);
const auto over = OverEmoji{ section, index };
const auto emoji = lookupOverEmoji(&over);
- const auto custom = lookupCustomEmoji(index, section);
+ const auto custom = lookupCustomEmoji(&over);
if (custom && custom->sticker()) {
const auto sticker = custom->sticker();
const auto emoji = sticker->alt;
@@ -1498,6 +1506,11 @@ bool EmojiListWidget::checkPickerHide() {
return false;
}
+DocumentData *EmojiListWidget::lookupCustomEmoji(
+ const OverEmoji *over) const {
+ return over ? lookupCustomEmoji(over->index, over->section) : nullptr;
+}
+
DocumentData *EmojiListWidget::lookupCustomEmoji(
int index,
int section) const {
@@ -1600,12 +1613,14 @@ void EmojiListWidget::mousePressEvent(QMouseEvent *e) {
if (!Core::App().settings().hasChosenEmojiVariant(emoji)) {
showPicker();
} else {
- _showPickerTimer.callOnce(500);
+ _previewTimer.cancel();
+ _showPickerTimer.callOnce(kColorPickerDelay);
}
+ } else if (lookupCustomEmoji(over)) {
+ _showPickerTimer.cancel();
+ _previewTimer.callOnce(QApplication::startDragTime());
}
}
-
- _previewTimer.callOnce(QApplication::startDragTime());
}
void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
@@ -1613,11 +1628,6 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
auto pressed = _pressed;
setPressed(v::null);
- if (_previewShown) {
- _previewShown = false;
- return;
- }
-
_lastMousePos = e->globalPos();
if (!_picker->isHidden()) {
if (_picker->rect().contains(_picker->mapFromGlobal(_lastMousePos))) {
@@ -1640,7 +1650,10 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
_picker->hide();
}
- if (v::is_null(_selected) || _selected != pressed) {
+ if (_previewShown) {
+ _previewShown = false;
+ return;
+ } else if (v::is_null(_selected) || _selected != pressed) {
return;
}
@@ -1659,7 +1672,7 @@ void EmojiListWidget::mouseReleaseEvent(QMouseEvent *e) {
return;
}
selectEmoji(lookupChosen(emoji, over));
- } else if (const auto custom = lookupCustomEmoji(index, section)) {
+ } else if (const auto custom = lookupCustomEmoji(over)) {
selectCustom(lookupChosen(custom, over));
}
} else if (const auto set = std::get_if(&pressed)) {
@@ -2492,8 +2505,10 @@ bool EmojiListWidget::eventHook(QEvent *e) {
}
void EmojiListWidget::updateSelected() {
- if ((!v::is_null(_pressed) || !v::is_null(_pickerSelected)) && !_previewShown) {
- return;
+ if (!v::is_null(_pressed) || !v::is_null(_pickerSelected)) {
+ if (!_previewShown) {
+ return;
+ }
}
auto newSelected = OverState{ v::null };
@@ -2542,7 +2557,7 @@ void EmojiListWidget::setSelected(OverState newSelected) {
const auto hasSelection = !v::is_null(_selected);
if (hasSelection && Core::App().settings().suggestEmoji()) {
- Ui::Tooltip::Show(350, this);
+ Ui::Tooltip::Show(1000, this);
}
setCursor(hasSelection ? style::cur_pointer : style::cur_default);
@@ -2552,34 +2567,16 @@ void EmojiListWidget::setSelected(OverState newSelected) {
} else {
_picker->showAnimated();
}
- }
-
- if (_previewShown && _pressed != _selected) {
+ } else if (_previewShown && _pressed != _selected) {
if (const auto over = std::get_if(&_selected)) {
- _pressed = _selected;
-
- const auto section = over ? over->section : -1;
- const auto index = over ? over->index : -1;
-
- if (const auto document = lookupCustomEmoji(index, section)) {
- _show->showMediaPreview(document->stickerSetOrigin(), document);
+ if (const auto custom = lookupCustomEmoji(over)) {
+ _pressed = _selected;
+ _show->showMediaPreview(custom->stickerSetOrigin(), custom);
}
}
}
}
-void EmojiListWidget::showPreview() {
- if (const auto over = std::get_if(&_selected)) {
- const auto section = over ? over->section : -1;
- const auto index = over ? over->index : -1;
-
- if (const auto document = lookupCustomEmoji(index, section)) {
- _show->showMediaPreview(document->stickerSetOrigin(), document);
- _previewShown = true;
- }
- }
-}
-
void EmojiListWidget::setPressed(OverState newPressed) {
if (auto button = std::get_if(&_pressed)) {
Assert(hasColorButton(button->section)
diff --git a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
index 647a2c789..9f35f1963 100644
--- a/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
+++ b/Telegram/SourceFiles/chat_helpers/emoji_list_widget.h
@@ -287,6 +287,8 @@ private:
int index);
[[nodiscard]] EmojiPtr lookupOverEmoji(const OverEmoji *over) const;
+ [[nodiscard]] DocumentData *lookupCustomEmoji(
+ const OverEmoji *over) const;
[[nodiscard]] DocumentData *lookupCustomEmoji(
int index,
int section) const;
@@ -371,6 +373,8 @@ private:
DocumentId documentId,
uint64 setId);
+ void showPreview();
+
void applyNextSearchQuery();
void showPreview();
@@ -442,6 +446,8 @@ private:
object_ptr _picker;
base::Timer _showPickerTimer;
+ base::Timer _previewTimer;
+ bool _previewShown = false;
rpl::event_stream _chosen;
rpl::event_stream _customChosen;
diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h
index 15a7dc1f9..e064d1e2d 100644
--- a/Telegram/SourceFiles/core/click_handler_types.h
+++ b/Telegram/SourceFiles/core/click_handler_types.h
@@ -46,6 +46,7 @@ struct ClickHandlerContext {
bool mayShowConfirmation = false;
bool skipBotAutoLogin = false;
bool botStartAutoSubmit = false;
+ bool ignoreIv = false;
// Is filled from peer info.
PeerData *peer = nullptr;
};
diff --git a/Telegram/SourceFiles/core/file_utilities.cpp b/Telegram/SourceFiles/core/file_utilities.cpp
index 26cc17279..1de1e028f 100644
--- a/Telegram/SourceFiles/core/file_utilities.cpp
+++ b/Telegram/SourceFiles/core/file_utilities.cpp
@@ -159,7 +159,7 @@ void Launch(const QString &filepath) {
void ShowInFolder(const QString &filepath) {
crl::on_main([=] {
Ui::PreventDelayedActivation();
- if (Platform::IsLinux()) {
+ if (Platform::IsX11()) {
// Hide mediaview to make other apps visible.
Core::App().hideMediaView();
}
diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp
index 83f798d44..37ab0fe91 100644
--- a/Telegram/SourceFiles/core/local_url_handlers.cpp
+++ b/Telegram/SourceFiles/core/local_url_handlers.cpp
@@ -567,6 +567,7 @@ bool ResolveUsernameOrPhone(
.phone = phone,
.messageId = post,
.storyId = storyId,
+ .text = params.value(u"text"_q),
.repliesInfo = commentId
? Window::RepliesByLinkInfo{
Window::CommentId{ commentId }
@@ -901,6 +902,34 @@ bool ShowEditPersonalChannel(
return true;
}
+bool ShowCollectiblePhone(
+ Window::SessionController *controller,
+ const Match &match,
+ const QVariant &context) {
+ if (!controller) {
+ return false;
+ }
+ const auto phone = match->captured(1);
+ const auto peerId = PeerId(match->captured(2).toULongLong());
+ controller->resolveCollectible(
+ peerId,
+ phone.startsWith('+') ? phone : '+' + phone);
+ return true;
+}
+
+bool ShowCollectibleUsername(
+ Window::SessionController *controller,
+ const Match &match,
+ const QVariant &context) {
+ if (!controller) {
+ return false;
+ }
+ const auto username = match->captured(1);
+ const auto peerId = PeerId(match->captured(2).toULongLong());
+ controller->resolveCollectible(peerId, username);
+ return true;
+}
+
void ExportTestChatTheme(
not_null controller,
not_null theme) {
@@ -1311,6 +1340,14 @@ const std::vector &InternalUrlHandlers() {
u"^edit_personal_channel$"_q,
ShowEditPersonalChannel,
},
+ {
+ u"^collectible_phone/([\\+0-9\\-\\s]+)@([0-9]+)$"_q,
+ ShowCollectiblePhone,
+ },
+ {
+ u"^collectible_username/([a-zA-Z0-9\\-\\_\\.]+)@([0-9]+)$"_q,
+ ShowCollectibleUsername,
+ },
};
return Result;
}
diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp
index fb145f7f1..6feacdb5b 100644
--- a/Telegram/SourceFiles/core/sandbox.cpp
+++ b/Telegram/SourceFiles/core/sandbox.cpp
@@ -33,7 +33,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include
#include
#include
-#include
namespace Core {
namespace {
@@ -518,10 +517,8 @@ void Sandbox::refreshGlobalProxy() {
|| proxy.type == MTP::ProxyData::Type::Http) {
QNetworkProxy::setApplicationProxy(
MTP::ToNetworkProxy(MTP::ToDirectIpProxy(proxy)));
- } else if ((!Core::IsAppLaunched()
- || Core::App().settings().proxy().isSystem())
- // this works stable only in sandboxed environment where it works through portal
- && (!Platform::IsLinux() || KSandbox::isInside() || cDebugMode())) {
+ } else if (!Core::IsAppLaunched()
+ || Core::App().settings().proxy().isSystem()) {
QNetworkProxyFactory::setUseSystemConfiguration(true);
} else {
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
diff --git a/Telegram/SourceFiles/core/ui_integration.cpp b/Telegram/SourceFiles/core/ui_integration.cpp
index 01763e1e2..5f0c200a7 100644
--- a/Telegram/SourceFiles/core/ui_integration.cpp
+++ b/Telegram/SourceFiles/core/ui_integration.cpp
@@ -16,6 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_custom_emoji.h"
#include "data/data_session.h"
#include "data/data_sponsored_messages.h"
+#include "iv/iv_instance.h"
#include "ui/text/text_custom_emoji.h"
#include "ui/basic_click_handlers.h"
#include "ui/emoji_config.h"
@@ -241,6 +242,13 @@ bool UiIntegration::handleUrlClick(
} else if (local.startsWith(u"internal:"_q, Qt::CaseInsensitive)) {
Core::App().openInternalUrl(local, context);
return true;
+ } else if (Iv::PreferForUri(url)
+ && !context.value().ignoreIv) {
+ const auto my = context.value();
+ if (const auto controller = my.sessionWindow.get()) {
+ Core::App().iv().openWithIvPreferred(controller, url, context);
+ return true;
+ }
}
if (AyuUrlHandlers::TryHandleSpotify(url)) {
diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h
index a0582bc4b..ffbc3b76c 100644
--- a/Telegram/SourceFiles/core/version.h
+++ b/Telegram/SourceFiles/core/version.h
@@ -22,7 +22,7 @@ constexpr auto AppId = "{53F49750-6209-4FBF-9CA8-7A333C87D666}"_cs;
constexpr auto AppNameOld = "AyuGram for Windows"_cs;
constexpr auto AppName = "AyuGram Desktop"_cs;
constexpr auto AppFile = "AyuGram"_cs;
-constexpr auto AppVersion = 4016000;
-constexpr auto AppVersionStr = "4.16";
+constexpr auto AppVersion = 4016006;
+constexpr auto AppVersionStr = "4.16.6";
constexpr auto AppBetaVersion = false;
constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION;
diff --git a/Telegram/SourceFiles/data/business/data_business_chatbots.cpp b/Telegram/SourceFiles/data/business/data_business_chatbots.cpp
index dd95482db..ca894acf5 100644
--- a/Telegram/SourceFiles/data/business/data_business_chatbots.cpp
+++ b/Telegram/SourceFiles/data/business/data_business_chatbots.cpp
@@ -125,8 +125,10 @@ void Chatbots::togglePaused(not_null peer, bool paused) {
return;
} else if (const auto settings = peer->barSettings()) {
peer->setBarSettings(paused
- ? (*settings | PeerBarSetting::BusinessBotPaused)
- : (*settings & ~PeerBarSetting::BusinessBotPaused));
+ ? ((*settings | PeerBarSetting::BusinessBotPaused)
+ & ~PeerBarSetting::BusinessBotCanReply)
+ : ((*settings & ~PeerBarSetting::BusinessBotPaused)
+ | PeerBarSetting::BusinessBotCanReply));
} else {
api->requestPeerSettings(peer);
}
diff --git a/Telegram/SourceFiles/data/business/data_business_common.cpp b/Telegram/SourceFiles/data/business/data_business_common.cpp
index 52c545bc4..149a3ee13 100644
--- a/Telegram/SourceFiles/data/business/data_business_common.cpp
+++ b/Telegram/SourceFiles/data/business/data_business_common.cpp
@@ -71,6 +71,13 @@ auto RecipientsFlags(const BusinessRecipients &data) {
} // namespace
+BusinessRecipients BusinessRecipients::MakeValid(BusinessRecipients value) {
+ if (value.included.empty()) {
+ value.allButExcluded = true;
+ }
+ return value;
+}
+
MTPInputBusinessRecipients ForMessagesToMTP(const BusinessRecipients &data) {
using Flag = MTPDinputBusinessRecipients::Flag;
const auto &chats = data.allButExcluded ? data.excluded : data.included;
diff --git a/Telegram/SourceFiles/data/business/data_business_common.h b/Telegram/SourceFiles/data/business/data_business_common.h
index d6f4121a6..600d5ff79 100644
--- a/Telegram/SourceFiles/data/business/data_business_common.h
+++ b/Telegram/SourceFiles/data/business/data_business_common.h
@@ -44,6 +44,9 @@ struct BusinessRecipients {
BusinessChats excluded;
bool allButExcluded = false;
+ [[nodiscard]] static BusinessRecipients MakeValid(
+ BusinessRecipients value);
+
friend inline bool operator==(
const BusinessRecipients &a,
const BusinessRecipients &b) = default;
diff --git a/Telegram/SourceFiles/data/data_birthday.cpp b/Telegram/SourceFiles/data/data_birthday.cpp
index 65707664f..34a2b0737 100644
--- a/Telegram/SourceFiles/data/data_birthday.cpp
+++ b/Telegram/SourceFiles/data/data_birthday.cpp
@@ -127,4 +127,3 @@ rpl::producer IsBirthdayTodayValue(Birthday date) {
}
} // namespace Data
-
diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp
index b32c85c53..b088be2d7 100644
--- a/Telegram/SourceFiles/data/data_channel.cpp
+++ b/Telegram/SourceFiles/data/data_channel.cpp
@@ -139,6 +139,10 @@ const std::vector &ChannelData::usernames() const {
return _username.usernames();
}
+bool ChannelData::isUsernameEditable(QString username) const {
+ return _username.isEditable(username);
+}
+
void ChannelData::setAccessHash(uint64 accessHash) {
access = accessHash;
input = MTP_inputPeerChannel(
diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h
index d22352020..6dbe84329 100644
--- a/Telegram/SourceFiles/data/data_channel.h
+++ b/Telegram/SourceFiles/data/data_channel.h
@@ -182,6 +182,7 @@ public:
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector &usernames() const;
+ [[nodiscard]] bool isUsernameEditable(QString username) const;
[[nodiscard]] int membersCount() const {
return std::max(_membersCount, 1);
diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp
index 95c78092d..2fa5f05d4 100644
--- a/Telegram/SourceFiles/data/data_download_manager.cpp
+++ b/Telegram/SourceFiles/data/data_download_manager.cpp
@@ -1123,13 +1123,14 @@ rpl::producer MakeDownloadBarContent() {
state->thumbnail = Images::Prepare(embed->original(), 0, {
.options = Images::Option::Blur,
});
+ } else if (!state->downloadTaskLifetime) {
+ state->document->session().downloaderTaskFinished(
+ ) | rpl::filter([=] {
+ return self(self);
+ }) | rpl::start_with_next(
+ state->push,
+ state->downloadTaskLifetime);
}
- state->document->session().downloaderTaskFinished(
- ) | rpl::filter([=] {
- return self(self);
- }) | rpl::start_with_next(
- state->push,
- state->downloadTaskLifetime);
return !state->thumbnail.isNull();
};
const auto resolveThumbnail = [=] {
diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp
index 76389df4e..9b5d6d379 100644
--- a/Telegram/SourceFiles/data/data_peer.cpp
+++ b/Telegram/SourceFiles/data/data_peer.cpp
@@ -940,7 +940,7 @@ const QString &PeerData::shortName() const {
return _name;
}
-QString PeerData::userName() const {
+QString PeerData::username() const {
if (const auto user = asUser()) {
return user->username();
} else if (const auto channel = asChannel()) {
@@ -949,6 +949,34 @@ QString PeerData::userName() const {
return QString();
}
+QString PeerData::editableUsername() const {
+ if (const auto user = asUser()) {
+ return user->editableUsername();
+ } else if (const auto channel = asChannel()) {
+ return channel->editableUsername();
+ }
+ return QString();
+}
+
+const std::vector &PeerData::usernames() const {
+ if (const auto user = asUser()) {
+ return user->usernames();
+ } else if (const auto channel = asChannel()) {
+ return channel->usernames();
+ }
+ static const auto kEmpty = std::vector();
+ return kEmpty;
+}
+
+bool PeerData::isUsernameEditable(QString username) const {
+ if (const auto user = asUser()) {
+ return user->isUsernameEditable(username);
+ } else if (const auto channel = asChannel()) {
+ return channel->isUsernameEditable(username);
+ }
+ return false;
+}
+
bool PeerData::changeColorIndex(uint8 index) {
index %= Ui::kColorIndexCount;
if (_colorIndexCloud && _colorIndex == index) {
diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h
index bf7d4e025..b7f98e599 100644
--- a/Telegram/SourceFiles/data/data_peer.h
+++ b/Telegram/SourceFiles/data/data_peer.h
@@ -279,7 +279,11 @@ public:
[[nodiscard]] const QString &name() const;
[[nodiscard]] const QString &shortName() const;
[[nodiscard]] const QString &topBarNameText() const;
- [[nodiscard]] QString userName() const;
+
+ [[nodiscard]] QString username() const;
+ [[nodiscard]] QString editableUsername() const;
+ [[nodiscard]] const std::vector &usernames() const;
+ [[nodiscard]] bool isUsernameEditable(QString username) const;
[[nodiscard]] const base::flat_set &nameWords() const {
return _nameWords;
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index 9a5160ea0..e21d11a65 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -1240,7 +1240,7 @@ PeerData *Session::peerByUsername(const QString &username) const {
const auto uname = username.trimmed();
for (const auto &[peerId, peer] : _peers) {
if (peer->isLoaded()
- && !peer->userName().compare(uname, Qt::CaseInsensitive)) {
+ && !peer->username().compare(uname, Qt::CaseInsensitive)) {
return peer.get();
}
}
diff --git a/Telegram/SourceFiles/data/data_sponsored_messages.cpp b/Telegram/SourceFiles/data/data_sponsored_messages.cpp
index b36258fb8..c22cce33a 100644
--- a/Telegram/SourceFiles/data/data_sponsored_messages.cpp
+++ b/Telegram/SourceFiles/data/data_sponsored_messages.cpp
@@ -309,7 +309,7 @@ void SponsoredMessages::append(
? _session->data().processBotApp(peerId, *data.vapp())
: nullptr;
result.botLinkInfo = Window::PeerByLinkInfo{
- .usernameOrId = user->userName(),
+ .usernameOrId = user->username(),
.resolveType = botAppData
? Window::ResolveType::BotApp
: data.vstart_param()
diff --git a/Telegram/SourceFiles/data/data_story.cpp b/Telegram/SourceFiles/data/data_story.cpp
index d4b12de6a..cc0fefe99 100644
--- a/Telegram/SourceFiles/data/data_story.cpp
+++ b/Telegram/SourceFiles/data/data_story.cpp
@@ -450,7 +450,7 @@ bool Story::hasDirectLink() const {
if (!_privacyPublic || (!_pinned && expired())) {
return false;
}
- return !_peer->userName().isEmpty();
+ return !_peer->username().isEmpty();
}
std::optional Story::errorTextForForward(
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index 97d480d10..c4e1bbf68 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -486,6 +486,10 @@ const std::vector &UserData::usernames() const {
return _username.usernames();
}
+bool UserData::isUsernameEditable(QString username) const {
+ return _username.isEditable(username);
+}
+
const QString &UserData::phone() const {
return _phone;
}
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index 7a8a22582..67da9e3f5 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -150,15 +150,11 @@ public:
// a full check by canShareThisContact() call.
[[nodiscard]] bool canShareThisContactFast() const;
- MTPInputUser inputUser = MTP_inputUserEmpty();
-
- QString firstName;
- QString lastName;
[[nodiscard]] const QString &phone() const;
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector &usernames() const;
- QString nameOrPhone;
+ [[nodiscard]] bool isUsernameEditable(QString username) const;
enum class ContactStatus : char {
Unknown,
@@ -186,8 +182,6 @@ public:
void setBirthday(Data::Birthday value);
void setBirthday(const tl::conditional &value);
- std::unique_ptr botInfo;
-
void setUnavailableReasons(
std::vector &&reasons);
@@ -209,6 +203,14 @@ public:
[[nodiscard]] MsgId personalChannelMessageId() const;
void setPersonalChannel(ChannelId channelId, MsgId messageId);
+ MTPInputUser inputUser = MTP_inputUserEmpty();
+
+ QString firstName;
+ QString lastName;
+ QString nameOrPhone;
+
+ std::unique_ptr botInfo;
+
private:
auto unavailableReasons() const
-> const std::vector & override;
diff --git a/Telegram/SourceFiles/data/data_user_names.cpp b/Telegram/SourceFiles/data/data_user_names.cpp
index b69756b5e..c2edd6b01 100644
--- a/Telegram/SourceFiles/data/data_user_names.cpp
+++ b/Telegram/SourceFiles/data/data_user_names.cpp
@@ -80,4 +80,10 @@ const std::vector &UsernamesInfo::usernames() const {
return _usernames;
}
+bool UsernamesInfo::isEditable(const QString &username) const {
+ return (_indexEditableUsername >= 0)
+ && (_indexEditableUsername < _usernames.size())
+ && (_usernames[_indexEditableUsername] == username);
+}
+
} // namespace Data
diff --git a/Telegram/SourceFiles/data/data_user_names.h b/Telegram/SourceFiles/data/data_user_names.h
index 03853b520..04912ced9 100644
--- a/Telegram/SourceFiles/data/data_user_names.h
+++ b/Telegram/SourceFiles/data/data_user_names.h
@@ -27,6 +27,7 @@ public:
[[nodiscard]] QString username() const;
[[nodiscard]] QString editableUsername() const;
[[nodiscard]] const std::vector &usernames() const;
+ [[nodiscard]] bool isEditable(const QString &username) const;
private:
std::vector _usernames;
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 354a148eb..0da825209 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -1087,7 +1087,7 @@ void InnerWidget::paintPeerSearchResult(
QRect tr(context.st->textLeft, context.st->textTop, namewidth, st::dialogsTextFont->height);
p.setFont(st::dialogsTextFont);
- QString username = peer->userName();
+ QString username = peer->username();
if (!context.active && username.startsWith(_peerSearchQuery, Qt::CaseInsensitive)) {
auto first = '@' + username.mid(0, _peerSearchQuery.size());
auto second = username.mid(_peerSearchQuery.size());
@@ -4021,7 +4021,7 @@ void InnerWidget::setupShortcuts() {
const auto history = thread->owningHistory();
const auto isArchived = history->folder()
&& (history->folder()->id() == Data::Folder::kId);
-
+
Window::ToggleHistoryArchived(
_controller->uiShow(),
history,
diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
index ab1f19958..5899117be 100644
--- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
+++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp
@@ -496,7 +496,7 @@ auto GenerateParticipantString(
data,
});
}
- const auto username = peer->userName();
+ const auto username = peer->username();
if (username.isEmpty()) {
return name;
}
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 68407be66..ae2ba9f8a 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -4536,7 +4536,7 @@ void HistoryWidget::chooseAttach(
}
const auto filter = (overrideSendImagesAsPhotos == true)
- ? FileDialog::ImagesOrAllFilter()
+ ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
@@ -6284,8 +6284,7 @@ void HistoryWidget::startItemRevealAnimations() {
void HistoryWidget::startMessageSendingAnimation(
not_null item) {
auto &sendingAnimation = controller()->sendingAnimation();
- if (!sendingAnimation.hasLocalMessage(item->fullId().msg)
- || !sendingAnimation.checkExpectedType(item)) {
+ if (!sendingAnimation.checkExpectedType(item)) {
return;
}
Assert(item->mainView() != nullptr);
@@ -6297,14 +6296,16 @@ void HistoryWidget::startMessageSendingAnimation(
geometryValue() | rpl::to_empty,
_scroll->geometryValue() | rpl::to_empty,
_list->geometryValue() | rpl::to_empty
- ) | rpl::map([=] {
+ ) | rpl::map([=]() -> std::optional {
const auto view = item->mainView();
+ const auto top = view ? _list->itemTop(view) : -1;
+ if (top < 0) {
+ return std::nullopt;
+ }
const auto additional = (_list->height() == _scroll->height())
? view->height()
: 0;
- return _list->mapToGlobal(QPoint(
- 0,
- _list->itemTop(view) - additional));
+ return _list->mapToGlobal(QPoint(0, top - additional));
});
sendingAnimation.startAnimation({
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index c7d63eeb8..76d3e6940 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/input_field.h"
#include "mtproto/sender.h"
-struct FileLoadResult;
enum class SendMediaType;
class MessageLinksParser;
struct InlineBotQuery;
diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
index 24177943f..a05a19f48 100644
--- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
+++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp
@@ -1973,6 +1973,7 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
const auto guard = gsl::finally([&] {
updateSendButtonType();
updateReplaceMediaButton();
+ updateFieldPlaceholder();
updateControlsVisibility();
updateControlsGeometry(_wrap->size());
});
diff --git a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
index 858b89fbc..524719f92 100644
--- a/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_contact_status.cpp
@@ -902,15 +902,15 @@ void BusinessBotStatus::Bar::showState(State state) {
_userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
_userpic->show();
_name->setText(state.bot->name());
- _status->setText(!state.canReply
- ? tr::lng_chatbot_status_views(tr::now)
- : state.paused
+ _status->setText(state.paused
? tr::lng_chatbot_status_paused(tr::now)
- : tr::lng_chatbot_status_can_reply(tr::now));
+ : state.canReply
+ ? tr::lng_chatbot_status_can_reply(tr::now)
+ : tr::lng_chatbot_status_views(tr::now));
_togglePaused->setText(state.paused
? tr::lng_chatbot_button_resume()
: tr::lng_chatbot_button_pause());
- _togglePaused->setVisible(state.canReply);
+ _togglePaused->setVisible(state.canReply || state.paused);
_paused = state.paused;
resizeToWidth(width());
}
diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
index d6e606db7..ac95a45a3 100644
--- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp
@@ -1839,16 +1839,18 @@ void ListWidget::startItemRevealAnimations() {
void ListWidget::startMessageSendingAnimation(
not_null item) {
auto &sendingAnimation = controller()->sendingAnimation();
- if (!sendingAnimation.hasLocalMessage(item->fullId().msg)
- || !sendingAnimation.checkExpectedType(item)) {
+ if (!sendingAnimation.checkExpectedType(item)) {
return;
}
auto globalEndTopLeft = rpl::merge(
session().data().newItemAdded() | rpl::to_empty,
geometryValue() | rpl::to_empty
- ) | rpl::map([=] {
+ ) | rpl::map([=]() -> std::optional {
const auto view = viewForItem(item);
+ if (!view) {
+ return std::nullopt;
+ }
const auto additional = !_visibleTop ? view->height() : 0;
return mapToGlobal(QPoint(0, itemTop(view) - additional));
});
diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
index b7ebe009b..91183100a 100644
--- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp
@@ -875,7 +875,7 @@ void RepliesWidget::chooseAttach(
}
const auto filter = (overrideSendImagesAsPhotos == true)
- ? FileDialog::ImagesOrAllFilter()
+ ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
FileDialog::OpenResult &&result) {
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
index 86c114195..df1a88f8c 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
@@ -62,11 +62,19 @@ namespace HistoryView {
ScheduledMemento::ScheduledMemento(not_null history)
: _history(history)
, _forumTopic(nullptr) {
+ const auto list = _history->owner().scheduledMessages().list(_history);
+ if (!list.ids.empty()) {
+ _list.setScrollTopState({ .item = { .fullId = list.ids.front() } });
+ }
}
ScheduledMemento::ScheduledMemento(not_null forumTopic)
: _history(forumTopic->owningHistory())
, _forumTopic(forumTopic) {
+ const auto list = _history->owner().scheduledMessages().list(_forumTopic);
+ if (!list.ids.empty()) {
+ _list.setScrollTopState({ .item = { .fullId = list.ids.front() } });
+ }
}
object_ptr ScheduledMemento::createWidget(
@@ -1162,9 +1170,7 @@ Context ScheduledWidget::listContext() {
}
bool ScheduledWidget::listScrollTo(int top, bool syntetic) {
- top = (top == ScrollMax && syntetic)
- ? 0
- : std::clamp(top, 0, _scroll->scrollTopMax());
+ top = std::clamp(top, 0, _scroll->scrollTopMax());
if (_scroll->scrollTop() == top) {
updateInnerVisibleArea();
return false;
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
index 850e56bb7..43702688f 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
@@ -280,7 +280,7 @@ private:
};
-class ScheduledMemento : public Window::SectionMemento {
+class ScheduledMemento final : public Window::SectionMemento {
public:
ScheduledMemento(not_null history);
ScheduledMemento(not_null forumTopic);
diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/earn_format.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/earn_format.cpp
index e79fa89ce..ea470af93 100644
--- a/Telegram/SourceFiles/info/channel_statistics/earn/earn_format.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/earn/earn_format.cpp
@@ -12,6 +12,7 @@ namespace Info::ChannelEarn {
using EarnInt = Data::EarnInt;
constexpr auto kMinorPartLength = 9;
+constexpr auto kMaxChoppedZero = kMinorPartLength - 2;
constexpr auto kZero = QChar('0');
constexpr auto kDot = QChar('.');
@@ -35,7 +36,7 @@ QString MinorPart(EarnInt value) {
auto ch = end - 1;
auto zeroCount = 0;
while (ch != begin) {
- if ((*ch) == kZero) {
+ if (((*ch) == kZero) && (zeroCount < kMaxChoppedZero)) {
zeroCount++;
} else {
break;
diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp
index 1a0cedb1b..80e377e1e 100644
--- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp
+++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.cpp
@@ -77,7 +77,7 @@ void ShowMenu(not_null box, const QString &text) {
[[nodiscard]] ClickHandlerPtr LearnMoreCurrencyLink(
not_null controller,
not_null box) {
- const auto url = u"https://telegram.org/blog/monetization-for-channels"_q;
+ const auto url = tr::lng_channel_earn_learn_coin_link(tr::now);
using Resolver = HistoryView::Controls::WebpageResolver;
const auto resolver = box->lifetime().make_state(
@@ -217,7 +217,18 @@ void AddRecipient(not_null box, const TextWithEntities &t) {
}
#endif
-[[nodiscard]] QImage IconCurrency(
+[[nodiscard]] QString FormatDate(const QDateTime &date) {
+ return tr::lng_group_call_starts_short_date(
+ tr::now,
+ lt_date,
+ langDayOfMonth(date.date()),
+ lt_time,
+ QLocale().toString(date.time(), QLocale::ShortFormat));
+}
+
+} // namespace
+
+QImage IconCurrency(
const style::FlatLabel &label,
const QColor &c) {
const auto s = Size(label.style.font->ascent);
@@ -234,17 +245,6 @@ void AddRecipient(not_null box, const TextWithEntities &t) {
return image;
}
-[[nodiscard]] QString FormatDate(const QDateTime &date) {
- return tr::lng_group_call_starts_short_date(
- tr::now,
- lt_date,
- langDayOfMonth(date.date()),
- lt_time,
- QLocale().toString(date.time(), QLocale::ShortFormat));
-}
-
-} // namespace
-
InnerWidget::InnerWidget(
QWidget *parent,
not_null controller,
@@ -372,6 +372,7 @@ void InnerWidget::fill() {
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
+ auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg);
p.drawEllipse(rect);
diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h
index 69d1587d8..99e9511be 100644
--- a/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h
+++ b/Telegram/SourceFiles/info/channel_statistics/earn/info_earn_inner_widget.h
@@ -23,6 +23,10 @@ namespace Info::ChannelEarn {
class Memento;
+[[nodiscard]] QImage IconCurrency(
+ const style::FlatLabel &label,
+ const QColor &c);
+
class InnerWidget final : public Ui::VerticalLayout {
public:
struct ShowRequest final {
diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
index df590a4bc..7da9da56b 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
@@ -123,21 +123,25 @@ base::options::toggle ShowPeerIdBelowAbout({
[[nodiscard]] Fn UsernamesLinkCallback(
not_null peer,
- std::shared_ptr show,
+ not_null controller,
const QString &addToLink) {
+ const auto weak = base::make_weak(controller);
return [=](QString link) {
- auto settings = &AyuSettings::getInstance();
- if (!settings->copyUsernameAsLink) {
- link = '@' + link.mid(13);
- } else {
- if (!link.startsWith(u"https://"_q)) {
- link = peer->session().createInternalLinkFull(peer->userName())
- + addToLink;
- }
+ if (link.startsWith(u"internal:"_q)) {
+ Core::App().openInternalUrl(link,
+ QVariant::fromValue(ClickHandlerContext{
+ .sessionWindow = weak,
+ }));
+ return;
+ } else if (!link.startsWith(u"https://"_q)) {
+ link = peer->session().createInternalLinkFull(peer->username())
+ + addToLink;
}
if (!link.isEmpty()) {
QGuiApplication::clipboard()->setText(link);
- show->showToast(tr::lng_username_copied(tr::now));
+ if (const auto window = weak.get()) {
+ window->showToast(tr::lng_username_copied(tr::now));
+ }
}
};
}
@@ -1055,16 +1059,13 @@ object_ptr DetailsFiller::setupInfo() {
UsernameValue(user, true) | rpl::map([=](TextWithEntities u) {
return u.text.isEmpty()
? TextWithEntities()
- : Ui::Text::Link(
- u,
- user->session().createInternalLinkFull(
- u.text.mid(1)));
+ : Ui::Text::Link(u, UsernameUrl(user, u.text.mid(1)));
}),
QString(),
st::infoProfileLabeledUsernamePadding);
const auto callback = UsernamesLinkCallback(
_peer,
- controller->uiShow(),
+ controller,
QString());
const auto hook = [=](Ui::FlatLabel::ContextMenuRequest request) {
if (!request.link) {
@@ -1108,7 +1109,7 @@ object_ptr DetailsFiller::setupInfo() {
}, copyUsername->lifetime());
copyUsername->setClickedCallback([=] {
const auto link = user->session().createInternalLinkFull(
- user->userName());
+ user->username());
if (!link.isEmpty()) {
QGuiApplication::clipboard()->setText(link);
controller->showToast(tr::lng_username_copied(tr::now));
@@ -1180,14 +1181,15 @@ object_ptr DetailsFiller::setupInfo() {
auto linkText = LinkValue(
_peer,
true
- ) | rpl::map([=](const QString &link) {
- return link.isEmpty()
+ ) | rpl::map([=](const LinkWithUrl &link) {
+ const auto text = link.text;
+ return text.isEmpty()
? TextWithEntities()
: Ui::Text::Link(
- (link.startsWith(u"https://"_q)
- ? link.mid(u"https://"_q.size())
- : link) + addToLink,
- link + addToLink);
+ (text.startsWith(u"https://"_q)
+ ? text.mid(u"https://"_q.size())
+ : text) + addToLink,
+ (addToLink.isEmpty() ? link.url : (text + addToLink)));
});
auto linkLine = addInfoOneLine(
(topicRootId
@@ -1198,7 +1200,7 @@ object_ptr DetailsFiller::setupInfo() {
const auto controller = _controller->parentController();
const auto linkCallback = UsernamesLinkCallback(
_peer,
- controller->uiShow(),
+ controller,
addToLink);
linkLine.text->overrideLinkClickHandler(linkCallback);
linkLine.subtext->overrideLinkClickHandler(linkCallback);
diff --git a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
index 58c51ed31..85fde827f 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.cpp
@@ -112,23 +112,22 @@ int TextItem::contentHeight() const {
} // namespace
-void AddPhoneMenu(not_null menu, not_null user) {
- if (user->isSelf()) {
- return;
- }
+bool IsCollectiblePhone(not_null user) {
using Strings = std::vector;
const auto prefixes = user->session().appConfig().get(
u"fragment_prefixes"_q,
- std::vector());
- {
- const auto proj = [&phone = user->phone()](const QString &p) {
- return phone.startsWith(p);
- };
- if (ranges::none_of(prefixes, proj)) {
- return;
- }
- }
- if (const auto url = AppConfig::FragmentLink(&user->session())) {
+ Strings{ u"888"_q });
+ const auto phone = user->phone();
+ const auto proj = [&](const QString &p) {
+ return phone.startsWith(p);
+ };
+ return ranges::any_of(prefixes, proj);
+}
+
+void AddPhoneMenu(not_null menu, not_null user) {
+ if (user->isSelf() || !IsCollectiblePhone(user)) {
+ return;
+ } else if (const auto url = AppConfig::FragmentLink(&user->session())) {
menu->addSeparator(&st::expandedMenuSeparator);
const auto link = Ui::Text::Link(
tr::lng_info_mobile_context_menu_fragment_about_link(tr::now),
diff --git a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
index 82ed6bb8c..25e6c8b5c 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_phone_menu.h
@@ -16,6 +16,8 @@ class PopupMenu;
namespace Info {
namespace Profile {
+[[nodiscard]] bool IsCollectiblePhone(not_null user);
+
void AddPhoneMenu(not_null menu, not_null user);
} // namespace Profile
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
index 9fe23da1f..3a0f9566e 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_chat_participants.h"
#include "apiwrap.h"
+#include "info/profile/info_profile_phone_menu.h"
#include "info/profile/info_profile_badge.h"
#include "core/application.h"
#include "core/click_handler_types.h"
@@ -55,7 +56,7 @@ auto PlainUsernameValue(not_null peer) {
peer->session().changes().peerFlagsValue(peer, UpdateFlag::Username),
peer->session().changes().peerFlagsValue(peer, UpdateFlag::Usernames)
) | rpl::map([=] {
- return peer->userName();
+ return peer->username();
});
}
@@ -136,14 +137,19 @@ rpl::producer PhoneOrHiddenValue(not_null user) {
PlainUsernameValue(user),
PlainAboutValue(user),
tr::lng_info_mobile_hidden()
- ) | rpl::map([](
+ ) | rpl::map([user](
const TextWithEntities &phone,
const QString &username,
const QString &about,
const QString &hidden) {
- return (phone.text.isEmpty() && username.isEmpty() && about.isEmpty())
- ? Ui::Text::WithEntities(hidden)
- : phone;
+ if (phone.text.isEmpty() && username.isEmpty() && about.isEmpty()) {
+ return Ui::Text::WithEntities(hidden);
+ } else if (IsCollectiblePhone(user)) {
+ return Ui::Text::Link(phone, u"internal:collectible_phone/"_q
+ + user->phone() + '@' + QString::number(user->id.value));
+ } else {
+ return phone;
+ }
});
}
@@ -160,15 +166,22 @@ rpl::producer UsernameValue(
}) | Ui::Text::ToWithEntities();
}
+QString UsernameUrl(not_null peer, const QString &username) {
+ return peer->isUsernameEditable(username)
+ ? peer->session().createInternalLinkFull(username)
+ : (u"internal:collectible_username/"_q
+ + username
+ + "@"
+ + QString::number(peer->id.value));
+}
+
rpl::producer> UsernamesValue(
not_null peer) {
const auto map = [=](const std::vector &usernames) {
return ranges::views::all(
usernames
) | ranges::views::transform([&](const QString &u) {
- return Ui::Text::Link(
- u,
- peer->session().createInternalLinkFull(u));
+ return Ui::Text::Link(u, UsernameUrl(peer, u));
}) | ranges::to_vector;
};
auto value = rpl::merge(
@@ -219,14 +232,19 @@ rpl::producer AboutValue(not_null peer) {
});
}
-rpl::producer LinkValue(not_null peer, bool primary) {
+rpl::producer LinkValue(not_null peer, bool primary) {
return (primary
? PlainPrimaryUsernameValue(peer)
: PlainUsernameValue(peer) | rpl::type_erased()
) | rpl::map([=](QString &&username) {
- return username.isEmpty()
- ? QString()
- : peer->session().createInternalLinkFull(username);
+ return LinkWithUrl{
+ .text = (username.isEmpty()
+ ? QString()
+ : peer->session().createInternalLinkFull(username)),
+ .url = (username.isEmpty()
+ ? QString()
+ : UsernameUrl(peer, username)),
+ };
});
}
diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h
index 6b7ccf475..72d50055a 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_values.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_values.h
@@ -61,14 +61,23 @@ rpl::producer> MigratedOrMeValue(
bool primary = false);
[[nodiscard]] rpl::producer> UsernamesValue(
not_null peer);
+[[nodiscard]] QString UsernameUrl(
+ not_null peer,
+ const QString &username);
[[nodiscard]] TextWithEntities AboutWithEntities(
not_null peer,
const QString &value);
[[nodiscard]] rpl::producer AboutValue(
not_null peer);
-[[nodiscard]] rpl::producer LinkValue(
+
+struct LinkWithUrl {
+ QString text;
+ QString url;
+};
+[[nodiscard]] rpl::producer LinkValue(
not_null peer,
bool primary = false);
+
[[nodiscard]] rpl::producer LocationValue(
not_null channel);
[[nodiscard]] rpl::producer NotificationsEnabledValue(
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
index 056aa224e..682afe3de 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.cpp
@@ -17,10 +17,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
+#include "data/data_web_page.h"
#include "main/main_session.h"
#include "main/main_domain.h"
#include "storage/storage_domain.h"
#include "info/profile/info_profile_values.h"
+#include "iv/iv_instance.h"
#include "ui/boxes/confirm_box.h"
#include "ui/chat/attach/attach_bot_webview.h"
#include "ui/widgets/checkbox.h"
@@ -653,6 +655,15 @@ void AttachWebView::botHandleMenuButton(Ui::BotWebView::MenuButton button) {
}
}
+void AttachWebView::botOpenIvLink(QString uri) {
+ const auto window = _context ? _context->controller.get() : nullptr;
+ if (window) {
+ Core::App().iv().openWithIvPreferred(window, uri);
+ } else {
+ Core::App().iv().openWithIvPreferred(_session, uri);
+ }
+}
+
void AttachWebView::botSendData(QByteArray data) {
if (!_context
|| _context->fromSwitch
diff --git a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
index d1d2c0e44..aebf99332 100644
--- a/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
+++ b/Telegram/SourceFiles/inline_bots/bot_attach_web_view.h
@@ -165,6 +165,7 @@ private:
bool botHandleLocalUri(QString uri, bool keepOpen) override;
void botHandleInvoice(QString slug) override;
void botHandleMenuButton(Ui::BotWebView::MenuButton button) override;
+ void botOpenIvLink(QString uri) override;
void botSendData(QByteArray data) override;
void botSwitchInlineQuery(
std::vector chatTypes,
diff --git a/Telegram/SourceFiles/iv/iv_data.cpp b/Telegram/SourceFiles/iv/iv_data.cpp
index bbcca0b2c..5258e6c9f 100644
--- a/Telegram/SourceFiles/iv/iv_data.cpp
+++ b/Telegram/SourceFiles/iv/iv_data.cpp
@@ -25,7 +25,7 @@ QByteArray GeoPointId(Geo point) {
const auto lon = int(point.lon * 1000000);
const auto combined = (std::uint64_t(std::uint32_t(lat)) << 32)
| std::uint64_t(std::uint32_t(lon));
- return QByteArray::number(combined)
+ return QByteArray::number(quint64(combined))
+ ','
+ QByteArray::number(point.access);
}
diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp
index 517415435..2362a6b61 100644
--- a/Telegram/SourceFiles/iv/iv_instance.cpp
+++ b/Telegram/SourceFiles/iv/iv_instance.cpp
@@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "core/file_utilities.h"
#include "core/shortcuts.h"
+#include "core/click_handler_types.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_cloud_file.h"
@@ -63,7 +64,7 @@ class Shown final : public base::has_weak_ptr {
public:
Shown(
not_null delegate,
- std::shared_ptr show,
+ not_null session,
not_null data,
QString hash);
@@ -169,12 +170,11 @@ private:
Shown::Shown(
not_null delegate,
- std::shared_ptr show,
+ not_null session,
not_null data,
QString hash)
: _delegate(delegate)
-, _session(&show->session())
-, _show(show) {
+, _session(session) {
prepare(data, hash);
}
@@ -818,7 +818,13 @@ void Instance::show(
std::shared_ptr show,
not_null data,
QString hash) {
- const auto session = &show->session();
+ this->show(&show->session(), data, hash);
+}
+
+void Instance::show(
+ not_null session,
+ not_null data,
+ QString hash) {
const auto guard = gsl::finally([&] {
if (data->partial()) {
requestFull(session, data->id());
@@ -828,10 +834,13 @@ void Instance::show(
_shown->moveTo(data, hash);
return;
}
- _shown = std::make_unique(_delegate, show, data, hash);
+ _shown = std::make_unique(_delegate, session, data, hash);
_shownSession = session;
_shown->events() | rpl::start_with_next([=](Controller::Event event) {
using Type = Controller::Event::Type;
+ const auto lower = event.url.toLower();
+ const auto urlChecked = lower.startsWith("http://")
+ || lower.startsWith("https://");
switch (event.type) {
case Type::Close:
_shown = nullptr;
@@ -846,7 +855,9 @@ void Instance::show(
processJoinChannel(event.context);
break;
case Type::OpenLinkExternal:
- File::OpenUrl(event.url);
+ if (urlChecked) {
+ File::OpenUrl(event.url);
+ }
closeAll();
break;
case Type::OpenMedia:
@@ -885,6 +896,9 @@ void Instance::show(
break;
case Type::OpenPage:
case Type::OpenLink:
+ if (!urlChecked) {
+ break;
+ }
_shownSession->api().request(MTPmessages_GetWebPage(
MTP_string(event.url),
MTP_int(0)
@@ -896,7 +910,7 @@ void Instance::show(
if (page && page->iv) {
const auto parts = event.url.split('#');
const auto hash = (parts.size() > 1) ? parts[1] : u""_q;
- this->show(show, page->iv.get(), hash);
+ this->show(_shownSession, page->iv.get(), hash);
} else {
UrlClickHandler::Open(event.url);
}
@@ -921,20 +935,104 @@ void Instance::show(
}
}, _shown->lifetime());
- if (!_tracking.contains(session)) {
- _tracking.emplace(session);
- session->lifetime().add([=] {
- _tracking.remove(session);
- _joining.remove(session);
- _fullRequested.remove(session);
- if (_shownSession == session) {
- _shownSession = nullptr;
- }
- if (_shown && _shown->showingFrom(session)) {
- _shown = nullptr;
- }
- });
+ trackSession(session);
+}
+
+void Instance::trackSession(not_null session) {
+ if (!_tracking.emplace(session).second) {
+ return;
}
+ session->lifetime().add([=] {
+ _tracking.remove(session);
+ _joining.remove(session);
+ _fullRequested.remove(session);
+ _ivCache.remove(session);
+ if (_ivRequestSession == session) {
+ session->api().request(_ivRequestId).cancel();
+ _ivRequestSession = nullptr;
+ _ivRequestUri = QString();
+ _ivRequestId = 0;
+ }
+ if (_shownSession == session) {
+ _shownSession = nullptr;
+ }
+ if (_shown && _shown->showingFrom(session)) {
+ _shown = nullptr;
+ }
+ });
+}
+
+void Instance::openWithIvPreferred(
+ not_null controller,
+ QString uri,
+ QVariant context) {
+ auto my = context.value();
+ my.sessionWindow = controller;
+ openWithIvPreferred(
+ &controller->session(),
+ uri,
+ QVariant::fromValue(my));
+}
+
+void Instance::openWithIvPreferred(
+ not_null session,
+ QString uri,
+ QVariant context) {
+ const auto openExternal = [=] {
+ auto my = context.value();
+ my.ignoreIv = true;
+ UrlClickHandler::Open(uri, QVariant::fromValue(my));
+ };
+ const auto parts = uri.split('#');
+ if (parts.isEmpty() || parts[0].isEmpty()) {
+ return;
+ } else if (!ShowButton()) {
+ return openExternal();
+ }
+ trackSession(session);
+ const auto hash = (parts.size() > 1) ? parts[1] : u""_q;
+ const auto url = parts[0];
+ auto &cache = _ivCache[session];
+ if (const auto i = cache.find(url); i != end(cache)) {
+ const auto page = i->second;
+ if (page && page->iv) {
+ auto my = context.value();
+ if (const auto window = my.sessionWindow.get()) {
+ show(window, page->iv.get(), hash);
+ } else {
+ show(session, page->iv.get(), hash);
+ }
+ } else {
+ openExternal();
+ }
+ return;
+ } else if (_ivRequestSession == session.get() && _ivRequestUri == uri) {
+ return;
+ } else if (_ivRequestId) {
+ _ivRequestSession->api().request(_ivRequestId).cancel();
+ }
+ const auto finish = [=](WebPageData *page) {
+ Expects(_ivRequestSession == session);
+
+ _ivRequestId = 0;
+ _ivRequestUri = QString();
+ _ivRequestSession = nullptr;
+ _ivCache[session][url] = page;
+ openWithIvPreferred(session, uri, context);
+ };
+ _ivRequestSession = session;
+ _ivRequestUri = uri;
+ _ivRequestId = session->api().request(MTPmessages_GetWebPage(
+ MTP_string(url),
+ MTP_int(0)
+ )).done([=](const MTPmessages_WebPage &result) {
+ const auto &data = result.data();
+ session->data().processUsers(data.vusers());
+ session->data().processChats(data.vchats());
+ finish(session->data().processWebpage(data.vwebpage()));
+ }).fail([=] {
+ finish(nullptr);
+ }).send();
}
void Instance::requestFull(
@@ -1028,4 +1126,17 @@ void Instance::closeAll() {
_shown = nullptr;
}
+bool PreferForUri(const QString &uri) {
+ const auto url = QUrl(uri);
+ const auto host = url.host().toLower();
+ const auto path = url.path().toLower();
+ return (host == u"telegra.ph"_q)
+ || (host == u"te.legra.ph"_q)
+ || (host == u"graph.org"_q)
+ || (host == u"telegram.org"_q
+ && (path.startsWith(u"/faq"_q)
+ || path.startsWith(u"/privacy"_q)
+ || path.startsWith(u"/blog"_q)));
+}
+
} // namespace Iv
diff --git a/Telegram/SourceFiles/iv/iv_instance.h b/Telegram/SourceFiles/iv/iv_instance.h
index ba3d8f72c..1299bbddd 100644
--- a/Telegram/SourceFiles/iv/iv_instance.h
+++ b/Telegram/SourceFiles/iv/iv_instance.h
@@ -36,6 +36,19 @@ public:
std::shared_ptr show,
not_null data,
QString hash);
+ void show(
+ not_null session,
+ not_null data,
+ QString hash);
+
+ void openWithIvPreferred(
+ not_null controller,
+ QString uri,
+ QVariant context = {});
+ void openWithIvPreferred(
+ not_null session,
+ QString uri,
+ QVariant context = {});
[[nodiscard]] bool hasActiveWindow(
not_null session) const;
@@ -52,6 +65,8 @@ private:
void processJoinChannel(const QString &context);
void requestFull(not_null session, const QString &id);
+ void trackSession(not_null session);
+
const not_null _delegate;
std::unique_ptr _shown;
@@ -64,8 +79,18 @@ private:
not_null,
base::flat_set> _fullRequested;
+ base::flat_map<
+ not_null,
+ base::flat_map> _ivCache;
+ Main::Session *_ivRequestSession = nullptr;
+ QString _ivRequestUri;
+ mtpRequestId _ivRequestId = 0;
+
+
rpl::lifetime _lifetime;
};
+[[nodiscard]] bool PreferForUri(const QString &uri);
+
} // namespace Iv
diff --git a/Telegram/SourceFiles/iv/iv_prepare.cpp b/Telegram/SourceFiles/iv/iv_prepare.cpp
index ff21e4e55..6069d4407 100644
--- a/Telegram/SourceFiles/iv/iv_prepare.cpp
+++ b/Telegram/SourceFiles/iv/iv_prepare.cpp
@@ -51,7 +51,7 @@ template >>
return Number(base::SafeRound(value * 10000.) / 100.);
};
-[[nodiscard]] QByteArray EscapeAttr(QByteArray value) {
+[[nodiscard]] QByteArray Escape(QByteArray value) {
auto result = QByteArray();
result.reserve(value.size());
for (const auto &ch : value) {
@@ -67,8 +67,8 @@ template >>
return result;
}
-[[nodiscard]] QByteArray EscapeText(QByteArray value) {
- return EscapeAttr(value);
+[[nodiscard]] QByteArray Date(TimeId date) {
+ return Escape(langDateTimeFull(base::unixtime::parse(date)).toUtf8());
}
class Parser final {
@@ -391,9 +391,7 @@ QByteArray Parser::block(const MTPDpageBlockSubtitle &data) {
QByteArray Parser::block(const MTPDpageBlockAuthorDate &data) {
auto inner = rich(data.vauthor());
if (const auto date = data.vpublished_date().v) {
- const auto parsed = base::unixtime::parse(date);
- inner += " \xE2\x80\xA2 "
- + tag("time", EscapeText(langDateTimeFull(parsed).toUtf8()));
+ inner += " \xE2\x80\xA2 " + tag("time", Date(date));
}
return tag("address", inner);
}
@@ -412,7 +410,7 @@ QByteArray Parser::block(const MTPDpageBlockParagraph &data) {
QByteArray Parser::block(const MTPDpageBlockPreformatted &data) {
auto list = Attributes();
- const auto language = EscapeAttr(utf(data.vlanguage()));
+ const auto language = utf(data.vlanguage());
if (!language.isEmpty()) {
list.push_back({ "data-language", language });
list.push_back({ "class", "lang-" + language });
@@ -430,7 +428,7 @@ QByteArray Parser::block(const MTPDpageBlockDivider &data) {
}
QByteArray Parser::block(const MTPDpageBlockAnchor &data) {
- return tag("a", { { "name", EscapeAttr(utf(data.vname())) } });
+ return tag("a", { { "name", utf(data.vname()) } });
}
QByteArray Parser::block(const MTPDpageBlockList &data) {
@@ -507,15 +505,13 @@ QByteArray Parser::block(
};
auto result = tag("div", attributes, inner);
- const auto href = data.vurl()
- ? utf(*data.vurl())
- : photoFullUrl(photo);
+ const auto href = data.vurl() ? utf(*data.vurl()) : photoFullUrl(photo);
const auto id = Number(photo.id);
result = tag("a", {
{ "href", href },
{ "oncontextmenu", data.vurl() ? QByteArray() : "return false;" },
{ "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id },
- }, result);
+ }, result);
if (!slideshow) {
result += caption(data.vcaption());
}
@@ -621,9 +617,9 @@ QByteArray Parser::block(const MTPDpageBlockEmbed &data) {
attributes.push_back({ "height", iframeHeight });
if (const auto url = data.vurl()) {
if (!autosize) {
- attributes.push_back({ "src", EscapeAttr(utf(url)) });
+ attributes.push_back({ "src", utf(url) });
} else {
- attributes.push_back({ "srcdoc", EscapeAttr(utf(url)) });
+ attributes.push_back({ "srcdoc", utf(url) });
}
} else if (const auto html = data.vhtml()) {
attributes.push_back({ "src", embedUrl(html->v) });
@@ -661,12 +657,10 @@ QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) {
address += tag(
"a",
{ { "rel", "author" }, { "onclick", "return false;" } },
- EscapeText(utf(data.vauthor())));
+ utf(data.vauthor()));
if (const auto date = data.vdate().v) {
const auto parsed = base::unixtime::parse(date);
- address += tag(
- "time",
- EscapeText(langDateTimeFull(parsed).toUtf8()));
+ address += tag("time", Date(date));
}
const auto inner = tag("address", address) + list(data.vblocks());
result = tag("blockquote", { { "class", "embed-post" } }, inner);
@@ -675,7 +669,7 @@ QByteArray Parser::block(const MTPDpageBlockEmbedPost &data) {
const auto inner = tag("strong", utf(data.vauthor()))
+ tag(
"small",
- tag("a", { { "href", EscapeAttr(url) } }, EscapeText(url)));
+ tag("a", { { "href", url } }, url));
result = tag("section", { { "class", "embed-post" } }, inner);
}
result += caption(data.vcaption());
@@ -732,7 +726,7 @@ QByteArray Parser::block(const MTPDpageBlockChannel &data) {
: "https://t.me/" + username;
result = tag(
"a",
- { { "href", EscapeAttr(link) }, { "data-context", "channel" + id } },
+ { { "href", link }, { "data-context", "channel" + id } },
result);
_result.channelIds.emplace(id);
return tag("section", {
@@ -839,23 +833,21 @@ QByteArray Parser::block(const MTPDpageRelatedArticle &data) {
inner += tag(
"span",
{ { "class", "related-link-title" } },
- EscapeText(utf(*title)));
+ utf(*title));
}
if (description) {
inner += tag(
"span",
{ { "class", "related-link-desc" } },
- EscapeText(utf(*description)));
+ utf(*description));
}
if (author || published) {
- const auto separator = (author && published) ? ", " : "";
- const auto parsed = base::unixtime::parse(published->v);
inner += tag(
"span",
{ { "class", "related-link-source" } },
- EscapeText((author ? utf(*author) : QByteArray())
- + separator
- + langDateTimeFull(parsed).toUtf8()));
+ ((author ? utf(*author) : QByteArray())
+ + ((author && published) ? ", " : QByteArray())
+ + (published ? Date(published->v) : QByteArray())));
}
result += tag("span", {
{ "class", "related-link-content" },
@@ -912,22 +904,25 @@ QByteArray Parser::block(const MTPDpageListItemBlocks &data) {
}
QByteArray Parser::block(const MTPDpageListOrderedItemText &data) {
- return tag("li", { { "value", utf(data.vnum()) } }, rich(data.vtext()));
+ return tag(
+ "li",
+ { { "value", utf(data.vnum()) } },
+ rich(data.vtext()));
}
QByteArray Parser::block(const MTPDpageListOrderedItemBlocks &data) {
return tag(
"li",
- { { "value", EscapeAttr(utf(data.vnum())) } },
+ { { "value", utf(data.vnum()) } },
list(data.vblocks()));
}
QByteArray Parser::utf(const MTPstring &text) {
- return text.v;
+ return Escape(text.v);
}
QByteArray Parser::utf(const tl::conditional &text) {
- return text ? text->v : QByteArray();
+ return text ? utf(*text) : QByteArray();
}
QByteArray Parser::tag(
@@ -965,7 +960,7 @@ QByteArray Parser::rich(const MTPRichText &text) {
{ "\xE2\x81\xA8", "" },
{ "\xE2\x81\xA9", "" },
};
- auto text = EscapeText(utf(data.vtext()));
+ auto text = utf(data.vtext());
for (const auto &[from, to] : replacements) {
text.replace(from, to);
}
@@ -1016,8 +1011,8 @@ QByteArray Parser::rich(const MTPRichText &text) {
}, rich(data.vtext()));
}, [&](const MTPDtextEmail &data) {
return tag("a", {
- { "href", "mailto:" + EscapeAttr(utf(data.vemail())) },
- }, rich(data.vtext()));
+ { "href", "mailto:" + utf(data.vemail()) },
+ }, rich(data.vtext()));
}, [&](const MTPDtextSubscript &data) {
return tag("sub", rich(data.vtext()));
}, [&](const MTPDtextSuperscript &data) {
@@ -1026,11 +1021,11 @@ QByteArray Parser::rich(const MTPRichText &text) {
return tag("mark", rich(data.vtext()));
}, [&](const MTPDtextPhone &data) {
return tag("a", {
- { "href", "tel:" + EscapeAttr(utf(data.vphone())) },
+ { "href", "tel:" + utf(data.vphone()) },
}, rich(data.vtext()));
}, [&](const MTPDtextAnchor &data) {
const auto inner = rich(data.vtext());
- const auto name = EscapeAttr(utf(data.vname()));
+ const auto name = utf(data.vname());
return inner.isEmpty()
? tag("a", { { "name", name } })
: tag(
diff --git a/Telegram/SourceFiles/main/main_domain.cpp b/Telegram/SourceFiles/main/main_domain.cpp
index 26e01229f..aafbcd2ac 100644
--- a/Telegram/SourceFiles/main/main_domain.cpp
+++ b/Telegram/SourceFiles/main/main_domain.cpp
@@ -32,11 +32,13 @@ Domain::Domain(const QString &dataName)
: _dataName(dataName)
, _local(std::make_unique(this, dataName)) {
_active.changes(
- ) | rpl::take(1) | rpl::start_with_next([] {
+ ) | rpl::take(1) | rpl::start_with_next([=] {
// In case we had a legacy passcoded app we start settings here.
Core::App().startSettingsAndBackground();
- Core::App().notifications().createManager();
+ crl::on_main(this, [=] {
+ Core::App().notifications().createManager();
+ });
}, _lifetime);
_active.changes(
@@ -51,7 +53,7 @@ Domain::Domain(const QString &dataName)
: rpl::never();
}) | rpl::flatten_latest(
) | rpl::start_with_next([](const Data::PeerUpdate &update) {
- CrashReports::SetAnnotation("Username", update.peer->userName());
+ CrashReports::SetAnnotation("Username", update.peer->username());
}, _lifetime);
}
diff --git a/Telegram/SourceFiles/main/main_domain.h b/Telegram/SourceFiles/main/main_domain.h
index 00ed7570e..8379a629a 100644
--- a/Telegram/SourceFiles/main/main_domain.h
+++ b/Telegram/SourceFiles/main/main_domain.h
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/timer.h"
+#include "base/weak_ptr.h"
namespace Storage {
class Domain;
@@ -23,7 +24,7 @@ namespace Main {
class Account;
class Session;
-class Domain final {
+class Domain final : public base::has_weak_ptr {
public:
struct AccountWithIndex {
int index = 0;
diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h
index c2ddd0d39..dba3ea8e6 100644
--- a/Telegram/SourceFiles/mainwidget.h
+++ b/Telegram/SourceFiles/mainwidget.h
@@ -16,7 +16,6 @@ struct HistoryMessageMarkupButton;
class MainWindow;
class HistoryWidget;
class StackItem;
-struct FileLoadResult;
class History;
class Image;
diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp
index 62404da81..a61a1c295 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_controller.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_controller.cpp
@@ -933,6 +933,13 @@ void Controller::show(
peer->updateFull();
}
+void Controller::jumpTo(
+ not_null story,
+ Data::StoriesContext context) {
+ show(story, std::move(context));
+ _delegate->storiesRedisplay(story);
+}
+
bool Controller::changeShown(Data::Story *story) {
const auto id = story ? story->fullId() : FullStoryId();
const auto session = story ? &story->session() : nullptr;
diff --git a/Telegram/SourceFiles/media/stories/media_stories_controller.h b/Telegram/SourceFiles/media/stories/media_stories_controller.h
index b8ad1e20b..b8745d4ed 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_controller.h
+++ b/Telegram/SourceFiles/media/stories/media_stories_controller.h
@@ -141,6 +141,7 @@ public:
-> HistoryView::Reactions::CachedIconFactory &;
void show(not_null story, Data::StoriesContext context);
+ void jumpTo(not_null story, Data::StoriesContext context);
void ready();
void updateVideoPlayback(const Player::TrackState &state);
diff --git a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp
index d6959d1a6..b1a33d9c1 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_reply.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_reply.cpp
@@ -431,7 +431,7 @@ void ReplyArea::chooseAttach(
}
const auto filter = (overrideSendImagesAsPhotos == true)
- ? FileDialog::ImagesOrAllFilter()
+ ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter();
const auto weak = make_weak(&_shownPeerGuard);
const auto callback = [=](FileDialog::OpenResult &&result) {
diff --git a/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp b/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp
index 608d7e6ab..05d7b588b 100644
--- a/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp
+++ b/Telegram/SourceFiles/media/stories/media_stories_repost_view.cpp
@@ -184,7 +184,7 @@ RepostClickHandler RepostView::lookupHandler(QPoint position) {
const auto of = owner->stories().lookup({ peer->id, id });
if (of) {
using namespace Data;
- _controller->show(*of, { StoriesContextSingle() });
+ _controller->jumpTo(*of, { StoriesContextSingle() });
} else {
_controller->uiShow()->show(PrepareShortInfoBox(peer));
}
diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
index 60c018a10..6d3ccf593 100644
--- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
+++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp
@@ -3262,7 +3262,7 @@ not_null OverlayWidget::widget() const {
void OverlayWidget::hide() {
clearBeforeHide();
- //applyHideWindowWorkaround();
+ applyHideWindowWorkaround();
_window->hide();
}
diff --git a/Telegram/SourceFiles/menu/menu_sponsored.cpp b/Telegram/SourceFiles/menu/menu_sponsored.cpp
index bb8109d2b..bfa8427f1 100644
--- a/Telegram/SourceFiles/menu/menu_sponsored.cpp
+++ b/Telegram/SourceFiles/menu/menu_sponsored.cpp
@@ -60,6 +60,7 @@ void AboutBox(
widget->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(widget);
+ auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::activeButtonBg);
p.drawEllipse(rect);
diff --git a/Telegram/SourceFiles/mtproto/facade.h b/Telegram/SourceFiles/mtproto/facade.h
index 989fdad4b..39e2f732c 100644
--- a/Telegram/SourceFiles/mtproto/facade.h
+++ b/Telegram/SourceFiles/mtproto/facade.h
@@ -40,10 +40,6 @@ constexpr ShiftedDcId groupCallStreamDcId(DcId dcId) {
return ShiftDcId(dcId, kGroupCallStreamDcShift);
}
-constexpr auto kUploadSessionsCount = 2;
-
-constexpr auto kUploadSessionsCountMax = 8;
-
namespace details {
constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
@@ -94,7 +90,6 @@ inline DcId getTemporaryIdFromRealDcId(ShiftedDcId shiftedDcId) {
namespace details {
constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
- static_assert(kUploadSessionsCountMax < kMaxMediaDcCount, "Too large MTPUploadSessionsCount!");
return ShiftDcId(dcId, kBaseUploadDcShift + index);
};
@@ -103,14 +98,14 @@ constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
// send(req, callbacks, MTP::uploadDcId(index)) - for upload shifted dc id
// uploading always to the main dc so BareDcId(result) == 0
inline ShiftedDcId uploadDcId(int index) {
- Expects(index >= 0 && index < kUploadSessionsCountMax);
+ Expects(index >= 0 && index < kMaxMediaDcCount);
return details::uploadDcId(0, index);
};
constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) {
return (shiftedDcId >= details::uploadDcId(0, 0))
- && (shiftedDcId < details::uploadDcId(0, kUploadSessionsCountMax - 1) + kDcShift);
+ && (shiftedDcId < details::uploadDcId(0, kMaxMediaDcCount - 1) + kDcShift);
}
inline ShiftedDcId destroyKeyNextDcId(ShiftedDcId shiftedDcId) {
diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp
index 861d6cd44..76c5cb623 100644
--- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp
+++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp
@@ -18,7 +18,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
#include "calls/calls_instance.h"
#include "main/main_account.h" // Account::configUpdated.
-#include "apiwrap.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "lang/lang_instance.h"
diff --git a/Telegram/SourceFiles/mtproto/session_private.cpp b/Telegram/SourceFiles/mtproto/session_private.cpp
index 729a95ed6..96a0c8268 100644
--- a/Telegram/SourceFiles/mtproto/session_private.cpp
+++ b/Telegram/SourceFiles/mtproto/session_private.cpp
@@ -60,6 +60,8 @@ constexpr auto kSendStateRequestWaiting = crl::time(1000);
// How much time to wait for some more requests, when sending msg acks.
constexpr auto kAckSendWaiting = 10 * crl::time(1000);
+constexpr auto kCutContainerOnSize = 16 * 1024;
+
auto SyncTimeRequestDuration = kFastRequestDuration;
using namespace details;
@@ -696,7 +698,8 @@ void SessionPrivate::tryToSend() {
initSize = initSizeInInts * sizeof(mtpPrime);
}
- bool needAnyResponse = false;
+ auto needAnyResponse = false;
+ auto someSkipped = false;
SerializedRequest toSendRequest;
{
QWriteLocker locker1(_sessionData->toSendMutex());
@@ -711,15 +714,33 @@ void SessionPrivate::tryToSend() {
locker1.unlock();
}
- uint32 toSendCount = toSend.size();
- if (pingRequest) ++toSendCount;
- if (ackRequest) ++toSendCount;
- if (resendRequest) ++toSendCount;
- if (stateRequest) ++toSendCount;
- if (httpWaitRequest) ++toSendCount;
- if (bindDcKeyRequest) ++toSendCount;
+ auto totalSending = int(toSend.size());
+ auto sendingFrom = begin(toSend);
+ auto sendingTill = end(toSend);
+ auto combinedLength = 0;
+ for (auto i = sendingFrom; i != sendingTill; ++i) {
+ combinedLength += i->second->size();
+ if (combinedLength >= kCutContainerOnSize) {
+ ++i;
+ if (const auto skipping = int(sendingTill - i)) {
+ sendingTill = i;
+ totalSending -= skipping;
+ Assert(totalSending > 0);
+ someSkipped = true;
+ }
+ break;
+ }
+ }
+ auto sendingRange = ranges::make_subrange(sendingFrom, sendingTill);
+ const auto sendingCount = totalSending;
+ if (pingRequest) ++totalSending;
+ if (ackRequest) ++totalSending;
+ if (resendRequest) ++totalSending;
+ if (stateRequest) ++totalSending;
+ if (httpWaitRequest) ++totalSending;
+ if (bindDcKeyRequest) ++totalSending;
- if (!toSendCount) {
+ if (!totalSending) {
return; // nothing to send
}
@@ -735,11 +756,11 @@ void SessionPrivate::tryToSend() {
? httpWaitRequest
: bindDcKeyRequest
? bindDcKeyRequest
- : toSend.begin()->second;
- if (toSendCount == 1 && !first->forceSendInContainer) {
+ : sendingRange.begin()->second;
+ if (totalSending == 1 && !first->forceSendInContainer) {
toSendRequest = first;
if (sendAll) {
- toSend.clear();
+ toSend.erase(sendingFrom, sendingTill);
locker1.unlock();
}
@@ -808,7 +829,7 @@ void SessionPrivate::tryToSend() {
if (stateRequest) containerSize += stateRequest.messageSize();
if (httpWaitRequest) containerSize += httpWaitRequest.messageSize();
if (bindDcKeyRequest) containerSize += bindDcKeyRequest.messageSize();
- for (const auto &[requestId, request] : toSend) {
+ for (const auto &[requestId, request] : sendingRange) {
containerSize += request.messageSize();
if (needsLayer && request->needsLayer) {
containerSize += initSizeInInts;
@@ -825,9 +846,9 @@ void SessionPrivate::tryToSend() {
// prepare container + each in invoke after
toSendRequest = SerializedRequest::Prepare(
containerSize,
- containerSize + 3 * toSend.size());
+ containerSize + 3 * sendingCount);
toSendRequest->push_back(mtpc_msg_container);
- toSendRequest->push_back(toSendCount);
+ toSendRequest->push_back(totalSending);
// check for a valid container
auto bigMsgId = base::unixtime::mtproto_msg_id();
@@ -839,7 +860,7 @@ void SessionPrivate::tryToSend() {
// prepare sent container
auto sentIdsWrap = SentContainer();
sentIdsWrap.sent = crl::now();
- sentIdsWrap.messages.reserve(toSendCount);
+ sentIdsWrap.messages.reserve(totalSending);
if (bindDcKeyRequest) {
_bindMsgId = placeToContainer(
@@ -848,6 +869,7 @@ void SessionPrivate::tryToSend() {
false,
bindDcKeyRequest);
_bindMessageSent = crl::now();
+ sentIdsWrap.messages.push_back(_bindMsgId);
needAnyResponse = true;
}
if (pingRequest) {
@@ -856,10 +878,11 @@ void SessionPrivate::tryToSend() {
bigMsgId,
forceNewMsgId,
pingRequest);
+ sentIdsWrap.messages.push_back(_pingMsgId);
needAnyResponse = true;
}
- for (auto &[requestId, request] : toSend) {
+ for (auto &[requestId, request] : sendingRange) {
const auto msgId = prepareToSend(
request,
bigMsgId,
@@ -904,7 +927,7 @@ void SessionPrivate::tryToSend() {
memcpy(toSendRequest->data() + from, request->constData() + 4, len * sizeof(mtpPrime));
}
}
- toSend.clear();
+ toSend.erase(sendingFrom, sendingTill);
if (stateRequest) {
const auto msgId = placeToContainer(
@@ -951,6 +974,11 @@ void SessionPrivate::tryToSend() {
}
}
sendSecureRequest(std::move(toSendRequest), needAnyResponse);
+ if (someSkipped) {
+ InvokeQueued(this, [=] {
+ tryToSend();
+ });
+ }
}
void SessionPrivate::retryByTimer() {
@@ -1108,9 +1136,6 @@ void SessionPrivate::onSentSome(uint64 size) {
DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain));
}
}
- if (isUploadDcId(_shiftedDcId)) {
- remain *= kUploadSessionsCount;
- }
_waitForReceivedTimer.callOnce(remain);
}
if (!_firstSentAt) {
@@ -1577,13 +1602,22 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
correctUnixtimeWithBadLocal(info.serverTime);
info.badTime = false;
}
+ if (_bindMsgId) {
+ LOG(("Message Info: bad message notification received"
+ " while binding temp key, restarting."));
+ return HandleResult::RestartConnection;
+ }
LOG(("Message Info: bad message notification received, msgId %1, error_code %2").arg(data.vbad_msg_id().v).arg(errorCode));
return HandleResult::ResetSession;
}
} else { // fatal (except 48, but it must not get here)
const auto badMsgId = mtpMsgId(data.vbad_msg_id().v);
const auto requestId = wasSent(resendId);
- if (requestId) {
+ if (_bindMsgId) {
+ LOG(("Message Error: fatal bad message notification received"
+ " while binding temp key, restarting."));
+ return HandleResult::RestartConnection;
+ } else if (requestId) {
LOG(("Message Error: "
"fatal bad message notification received, "
"msgId %1, error_code %2, requestId: %3"
@@ -1628,7 +1662,9 @@ SessionPrivate::HandleResult SessionPrivate::handleOneReceived(
}
_sessionSalt = data.vnew_server_salt().v;
- correctUnixtimeWithBadLocal(info.serverTime);
+
+ // Don't force time update here.
+ base::unixtime::update(info.serverTime);
if (_bindMsgId) {
LOG(("Message Info: bad_server_salt received while binding temp key, restarting."));
@@ -2056,7 +2092,7 @@ void SessionPrivate::correctUnixtimeByFastRequest(
locker.unlock();
SyncTimeRequestDuration = duration;
- base::unixtime::update(serverTime, true);
+ base::unixtime::update(serverTime);
return;
}
}
diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp
index 684163163..980888ffb 100644
--- a/Telegram/SourceFiles/passport/passport_form_controller.cpp
+++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp
@@ -1581,14 +1581,10 @@ void FormController::uploadEncryptedFile(
&session(),
std::make_unique(std::move(data)));
- auto prepared = std::make_shared(
- TaskId(),
- file.uploadData->fileId,
- FileLoadTo(PeerId(), Api::SendOptions(), FullReplyTo(), MsgId()),
- TextWithTags(),
- false,
- std::shared_ptr(nullptr));
- prepared->type = SendMediaType::Secure;
+ auto prepared = MakePreparedFile({
+ .id = file.uploadData->fileId,
+ .type = SendMediaType::Secure,
+ });
prepared->content = QByteArray::fromRawData(
reinterpret_cast(file.uploadData->bytes.data()),
file.uploadData->bytes.size());
diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
index 1242c751f..0ea48a7de 100644
--- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp
@@ -37,6 +37,7 @@ namespace Notifications {
namespace {
using namespace gi::repository;
+namespace GObject = gi::repository::GObject;
constexpr auto kService = "org.freedesktop.Notifications";
constexpr auto kObjectPath = "/org/freedesktop/Notifications";
diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
index 711dda7a9..fe60e831d 100644
--- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp
+++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp
@@ -510,7 +510,7 @@ QString SingleInstanceLocalServerName(const QString &hash) {
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
std::optional IsDarkMode() {
- const auto result = base::Platform::XDP::ReadSetting(
+ auto result = base::Platform::XDP::ReadSetting(
"org.freedesktop.appearance",
"color-scheme");
diff --git a/Telegram/SourceFiles/settings/business/settings_away_message.cpp b/Telegram/SourceFiles/settings/business/settings_away_message.cpp
index afaeb3a2b..58f3a6165 100644
--- a/Telegram/SourceFiles/settings/business/settings_away_message.cpp
+++ b/Telegram/SourceFiles/settings/business/settings_away_message.cpp
@@ -220,7 +220,7 @@ void AwayMessage::setupContent(
_recipients = disabled
? Data::BusinessRecipients{ .allButExcluded = true }
- : current.recipients;
+ : Data::BusinessRecipients::MakeValid(current.recipients);
auto initialSchedule = disabled ? AwaySchedule{
.type = AwayScheduleType::Always,
} : current.schedule;
diff --git a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp
index 16072b1f0..7bd61ca90 100644
--- a/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp
+++ b/Telegram/SourceFiles/settings/business/settings_chat_intro.cpp
@@ -160,7 +160,7 @@ private:
object_ptr(
container,
st::settingsChatIntroField,
- tr::lng_chat_intro_enter_title(),
+ std::move(placeholder),
current),
st::settingsChatIntroFieldMargins);
field->setMaxLength(limit);
diff --git a/Telegram/SourceFiles/settings/business/settings_chatbots.cpp b/Telegram/SourceFiles/settings/business/settings_chatbots.cpp
index d31239e19..3c75cf9ba 100644
--- a/Telegram/SourceFiles/settings/business/settings_chatbots.cpp
+++ b/Telegram/SourceFiles/settings/business/settings_chatbots.cpp
@@ -408,7 +408,7 @@ void Chatbots::setupContent(
const auto content = Ui::CreateChild(this);
const auto current = controller->session().data().chatbots().current();
- _recipients = current.recipients;
+ _recipients = Data::BusinessRecipients::MakeValid(current.recipients);
_repliesAllowed = current.repliesAllowed;
AddDividerTextWithLottie(content, {
diff --git a/Telegram/SourceFiles/settings/business/settings_greeting.cpp b/Telegram/SourceFiles/settings/business/settings_greeting.cpp
index 052e97a39..8437acc7c 100644
--- a/Telegram/SourceFiles/settings/business/settings_greeting.cpp
+++ b/Telegram/SourceFiles/settings/business/settings_greeting.cpp
@@ -125,7 +125,7 @@ void Greeting::setupContent(
_recipients = disabled
? Data::BusinessRecipients{ .allButExcluded = true }
- : current.recipients;
+ : Data::BusinessRecipients::MakeValid(current.recipients);
_noActivityDays = disabled
? kDefaultNoActivityDays
: current.noActivityDays;
diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp
index 9422c892c..efeffaac9 100644
--- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp
+++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp
@@ -1428,7 +1428,7 @@ void ShortcutMessages::chooseAttach(
_choosingAttach = false;
const auto filter = (overrideSendImagesAsPhotos == true)
- ? FileDialog::ImagesOrAllFilter()
+ ? FileDialog::PhotoVideoFilesFilter()
: FileDialog::AllOrImagesFilter();
FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
FileDialog::OpenResult &&result) {
diff --git a/Telegram/SourceFiles/settings/settings_intro.cpp b/Telegram/SourceFiles/settings/settings_intro.cpp
index bd38b8c3f..6ddb33f75 100644
--- a/Telegram/SourceFiles/settings/settings_intro.cpp
+++ b/Telegram/SourceFiles/settings/settings_intro.cpp
@@ -11,10 +11,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_main.h"
#include "settings/settings_chat.h"
#include "settings/settings_codes.h"
+#include "ui/basic_click_handlers.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/widgets/shadow.h"
-#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/scroll_area.h"
#include "ui/cached_round_corners.h"
@@ -104,7 +104,14 @@ object_ptr CreateIntroSettings(
Ui::AddDivider(result);
Ui::AddSkip(result);
- SetupFaq(result, false);
+
+ AddButtonWithIcon(
+ result,
+ tr::lng_settings_faq(),
+ st::settingsButtonNoIcon
+ )->addClickHandler([] {
+ OpenFaq(nullptr);
+ });
return result;
}
diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp
index 9a705637f..4ac873853 100644
--- a/Telegram/SourceFiles/settings/settings_main.cpp
+++ b/Telegram/SourceFiles/settings/settings_main.cpp
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_main.h"
#include "core/application.h"
+#include "core/click_handler_types.h"
#include "settings/settings_business.h"
#include "settings/settings_codes.h"
#include "settings/settings_chat.h"
@@ -26,13 +27,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/basic_click_handlers.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/userpic_button.h"
-#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
-#include "ui/wrap/padding_wrap.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
-#include "ui/widgets/labels.h"
#include "ui/widgets/continuous_sliders.h"
-#include "ui/widgets/buttons.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/new_badges.h"
@@ -43,9 +40,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "data/data_chat_filters.h"
-#include "data/data_peer_values.h" // Data::AmPremiumValue
#include "lang/lang_cloud_manager.h"
-#include "lang/lang_keys.h"
#include "lang/lang_instance.h"
#include "storage/localstorage.h"
#include "main/main_session.h"
@@ -62,13 +57,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_values.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
-#include "core/file_utilities.h"
-#include "core/application.h"
#include "base/call_delayed.h"
-#include "base/unixtime.h"
#include "base/platform/base_platform_info.h"
#include "styles/style_settings.h"
-#include "styles/style_boxes.h"
#include "styles/style_info.h"
#include "styles/style_menu_icons.h"
@@ -229,7 +220,7 @@ void Cover::initViewers() {
}, lifetime());
_username->overrideLinkClickHandler([=] {
- const auto username = _user->userName();
+ const auto username = _user->username();
if (username.isEmpty()) {
_controller->show(Box(UsernamesBox, _user));
} else {
@@ -625,26 +616,20 @@ void SetupInterfaceScale(
}
}
-void OpenFaq() {
- File::OpenUrl(telegramFaqLink());
-}
-
-void SetupFaq(not_null container, bool icon) {
- AddButtonWithIcon(
- container,
- tr::lng_settings_faq(),
- icon ? st::settingsButton : st::settingsButtonNoIcon,
- { icon ? &st::menuIconFaq : nullptr }
- )->addClickHandler(OpenFaq);
-}
-
void SetupHelp(
not_null controller,
not_null container) {
Ui::AddDivider(container);
Ui::AddSkip(container);
- SetupFaq(container);
+ AddButtonWithIcon(
+ container,
+ tr::lng_settings_faq(),
+ st::settingsButton,
+ { &st::menuIconFaq }
+ )->addClickHandler([=] {
+ OpenFaq(controller);
+ });
AddButtonWithIcon(
container,
@@ -688,7 +673,10 @@ void SetupHelp(
auto box = Ui::MakeConfirmBox({
.text = tr::lng_settings_ask_sure(),
.confirmed = sure,
- .cancelled = OpenFaq,
+ .cancelled = [=](Fn close) {
+ OpenFaq(controller);
+ close();
+ },
.confirmText = tr::lng_settings_ask_ok(),
.cancelText = tr::lng_settings_faq_button(),
.strictCancel = true,
@@ -767,4 +755,12 @@ void Main::setupContent(not_null controller) {
controller->session().data().cloudThemes().refresh();
}
+void OpenFaq(base::weak_ptr weak) {
+ UrlClickHandler::Open(
+ tr::lng_settings_faq_link(tr::now),
+ QVariant::fromValue(ClickHandlerContext{
+ .sessionWindow = weak,
+ }));
+}
+
} // namespace Settings
diff --git a/Telegram/SourceFiles/settings/settings_main.h b/Telegram/SourceFiles/settings/settings_main.h
index 7040fb9fa..a0f7e15ca 100644
--- a/Telegram/SourceFiles/settings/settings_main.h
+++ b/Telegram/SourceFiles/settings/settings_main.h
@@ -28,9 +28,8 @@ void SetupInterfaceScale(
not_null window,
not_null container,
bool icon = true);
-void SetupFaq(
- not_null container,
- bool icon = true);
+
+void OpenFaq(base::weak_ptr weak);
class Main : public Section {
public:
diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp
index 97a1ce913..95c4b0a1b 100644
--- a/Telegram/SourceFiles/storage/file_upload.cpp
+++ b/Telegram/SourceFiles/storage/file_upload.cpp
@@ -23,15 +23,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h"
#include "apiwrap.h"
-// AyuGram includes
-#include "ayu/ayu_settings.h"
-
-
namespace Storage {
namespace {
-// max 512kb uploaded at the same time in each session
-constexpr auto kMaxUploadFileParallelSize = MTP::kUploadSessionsCount * 512 * 1024;
+// max 1mb uploaded at the same time in each session
+constexpr auto kMaxUploadPerSession = 1024 * 1024;
constexpr auto kDocumentMaxPartsCountDefault = 4000;
@@ -51,84 +47,89 @@ constexpr auto kDocumentUploadPartSize3 = 256 * 1024;
constexpr auto kDocumentUploadPartSize4 = 512 * 1024;
// One part each half second, if not uploaded faster.
-constexpr auto kUploadRequestInterval = crl::time(500);
+constexpr auto kUploadRequestInterval = crl::time(250);
// How much time without upload causes additional session kill.
constexpr auto kKillSessionTimeout = 15 * crl::time(1000);
+// How much wait after session kill before killing another one.
+constexpr auto kWaitForNormalizeTimeout = 8 * crl::time(1000);
+
+constexpr auto kMaxSessionsCount = 8;
+constexpr auto kFastRequestThreshold = 1 * crl::time(1000);
+constexpr auto kSlowRequestThreshold = 8 * crl::time(1000);
+
+// Request is 'fast' if it was done in less than 1s and
+// (it-s size + queued before size) >= 512kb.
+constexpr auto kAcceptAsFastIfTotalAtLeast = 512 * 1024;
+
[[nodiscard]] const char *ThumbnailFormat(const QString &mime) {
return Core::IsMimeSticker(mime) ? "WEBP" : "JPG";
}
-int UploadSessionsCount() {
- const auto settings = &AyuSettings::getInstance();
- static const auto count = settings->uploadSpeedBoost ? 8 : 2;
- return count;
-}
-
-int UploadSessionsInterval() {
- const auto settings = &AyuSettings::getInstance();
- static const auto interval = settings->uploadSpeedBoost ? 25 : kUploadRequestInterval;
- return interval;
-}
-
} // namespace
-struct Uploader::File {
- File(const SendMediaReady &media);
- File(const std::shared_ptr &file);
+struct Uploader::Entry {
+ Entry(FullMsgId itemId, const std::shared_ptr &file);
void setDocSize(int64 size);
- bool setPartSize(uint32 partSize);
+ bool setPartSize(int partSize);
- std::shared_ptr file;
- SendMediaReady media;
- int32 partsCount = 0;
- mutable int64 fileSentSize = 0;
+ // const, but non-const for the move-assignment in the
+ FullMsgId itemId;
+ std::shared_ptr file;
+ not_null*> parts;
+ uint64 partsOfId = 0;
- uint64 id() const;
- SendMediaType type() const;
- uint64 thumbId() const;
- const QString &filename() const;
+ int64 sentSize = 0;
+ ushort partsSent = 0;
+ ushort partsWaiting = 0;
HashMd5 md5Hash;
std::unique_ptr docFile;
int64 docSize = 0;
- int64 docPartSize = 0;
- int docSentParts = 0;
- int docPartsCount = 0;
+ int64 docSentSize = 0;
+ int docPartSize = 0;
+ ushort docPartsSent = 0;
+ ushort docPartsCount = 0;
+ ushort docPartsWaiting = 0;
};
-Uploader::File::File(const SendMediaReady &media) : media(media) {
- partsCount = media.parts.size();
- if (type() == SendMediaType::File
- || type() == SendMediaType::ThemeFile
- || type() == SendMediaType::Audio) {
- setDocSize(media.file.isEmpty()
- ? media.data.size()
- : media.filesize);
- } else {
- docSize = docPartSize = docPartsCount = 0;
- }
-}
-Uploader::File::File(const std::shared_ptr &file)
-: file(file) {
- partsCount = (type() == SendMediaType::Photo
- || type() == SendMediaType::Secure)
- ? file->fileparts.size()
- : file->thumbparts.size();
- if (type() == SendMediaType::File
- || type() == SendMediaType::ThemeFile
- || type() == SendMediaType::Audio) {
+struct Uploader::Request {
+ FullMsgId itemId;
+ crl::time sent = 0;
+ QByteArray bytes;
+ int queued = 0;
+ ushort part = 0;
+ uchar dcIndex = 0;
+ bool docPart = false;
+ bool bigPart = false;
+ bool nonPremiumDelayed = false;
+};
+
+Uploader::Entry::Entry(
+ FullMsgId itemId,
+ const std::shared_ptr &file)
+: itemId(itemId)
+, file(file)
+, parts((file->type == SendMediaType::Photo
+ || file->type == SendMediaType::Secure)
+ ? &file->fileparts
+ : &file->thumbparts)
+, partsOfId((file->type == SendMediaType::Photo
+ || file->type == SendMediaType::Secure)
+ ? file->id
+ : file->thumbId) {
+ if (file->type == SendMediaType::File
+ || file->type == SendMediaType::ThemeFile
+ || file->type == SendMediaType::Audio) {
setDocSize(file->filesize);
- } else {
- docSize = docPartSize = docPartsCount = 0;
}
}
-void Uploader::File::setDocSize(int64 size) {
+void Uploader::Entry::setDocSize(int64 size) {
docSize = size;
constexpr auto limit0 = 1024 * 1024;
constexpr auto limit1 = 32 * limit0;
@@ -143,32 +144,15 @@ void Uploader::File::setDocSize(int64 size) {
}
}
-bool Uploader::File::setPartSize(uint32 partSize) {
+bool Uploader::Entry::setPartSize(int partSize) {
docPartSize = partSize;
- docPartsCount = (docSize / docPartSize)
- + ((docSize % docPartSize) ? 1 : 0);
+ docPartsCount = (docSize + docPartSize - 1) / docPartSize;
return (docPartsCount <= kDocumentMaxPartsCountDefault);
}
-uint64 Uploader::File::id() const {
- return file ? file->id : media.id;
-}
-
-SendMediaType Uploader::File::type() const {
- return file ? file->type : media.type;
-}
-
-uint64 Uploader::File::thumbId() const {
- return file ? file->thumbId : media.thumbId;
-}
-
-const QString &Uploader::File::filename() const {
- return file ? file->filename : media.filename;
-}
-
Uploader::Uploader(not_null api)
: _api(api)
-, _nextTimer([=] { sendNext(); })
+, _nextTimer([=] { maybeSend(); })
, _stopSessionsTimer([=] { stopSessions(); }) {
const auto session = &_api->session();
photoReady(
@@ -225,22 +209,21 @@ Uploader::Uploader(not_null api)
_api->instance().nonPremiumDelayedRequests(
) | rpl::start_with_next([=](mtpRequestId id) {
- if (dcMap.contains(id)) {
- _nonPremiumDelayed.emplace(id);
+ const auto i = _requests.find(id);
+ if (i != end(_requests)) {
+ i->second.nonPremiumDelayed = true;
}
}, _lifetime);
}
-void Uploader::processPhotoProgress(const FullMsgId &newId) {
- const auto session = &_api->session();
- if (const auto item = session->data().message(newId)) {
+void Uploader::processPhotoProgress(FullMsgId itemId) {
+ if (const auto item = session().data().message(itemId)) {
sendProgressUpdate(item, Api::SendProgressType::UploadPhoto);
}
}
-void Uploader::processDocumentProgress(const FullMsgId &newId) {
- const auto session = &_api->session();
- if (const auto item = session->data().message(newId)) {
+void Uploader::processDocumentProgress(FullMsgId itemId) {
+ if (const auto item = session().data().message(itemId)) {
const auto media = item->media();
const auto document = media ? media->document() : nullptr;
const auto sendAction = (document && document->isVoiceMessage())
@@ -254,16 +237,14 @@ void Uploader::processDocumentProgress(const FullMsgId &newId) {
}
}
-void Uploader::processPhotoFailed(const FullMsgId &newId) {
- const auto session = &_api->session();
- if (const auto item = session->data().message(newId)) {
+void Uploader::processPhotoFailed(FullMsgId itemId) {
+ if (const auto item = session().data().message(itemId)) {
sendProgressUpdate(item, Api::SendProgressType::UploadPhoto, -1);
}
}
-void Uploader::processDocumentFailed(const FullMsgId &newId) {
- const auto session = &_api->session();
- if (const auto item = session->data().message(newId)) {
+void Uploader::processDocumentFailed(FullMsgId itemId) {
+ if (const auto item = session().data().message(itemId)) {
const auto media = item->media();
const auto document = media ? media->document() : nullptr;
const auto sendAction = (document && document->isVoiceMessage())
@@ -298,39 +279,13 @@ Main::Session &Uploader::session() const {
return _api->session();
}
-void Uploader::uploadMedia(
- const FullMsgId &msgId,
- const SendMediaReady &media) {
- if (media.type == SendMediaType::Photo) {
- session().data().processPhoto(media.photo, media.photoThumbs);
- } else if (media.type == SendMediaType::File
- || media.type == SendMediaType::ThemeFile
- || media.type == SendMediaType::Audio) {
- const auto document = media.photoThumbs.empty()
- ? session().data().processDocument(media.document)
- : session().data().processDocument(
- media.document,
- Images::FromImageInMemory(
- media.photoThumbs.front().second.image,
- "JPG",
- media.photoThumbs.front().second.bytes));
- if (!media.data.isEmpty()) {
- document->setDataAndCache(media.data);
- if (media.type == SendMediaType::ThemeFile) {
- document->checkWallPaperProperties();
- }
- }
- if (!media.file.isEmpty()) {
- document->setLocation(Core::FileLocation(media.file));
- }
- }
- queue.emplace(msgId, File(media));
- sendNext();
+FullMsgId Uploader::currentUploadId() const {
+ return _queue.empty() ? FullMsgId() : _queue.front().itemId;
}
void Uploader::upload(
- const FullMsgId &msgId,
- const std::shared_ptr &file) {
+ FullMsgId itemId,
+ const std::shared_ptr &file) {
if (file->type == SendMediaType::Photo) {
const auto photo = session().data().processPhoto(
file->photo,
@@ -375,414 +330,580 @@ void Uploader::upload(
document->checkWallPaperProperties();
}
}
- queue.emplace(msgId, File(file));
- sendNext();
+ _queue.push_back({ itemId, file });
+ if (!_nextTimer.isActive()) {
+ maybeSend();
+ }
}
-void Uploader::currentFailed() {
- auto j = queue.find(uploadingId);
- if (j != queue.end()) {
- const auto [msgId, file] = std::move(*j);
- queue.erase(j);
- notifyFailed(msgId, file);
+void Uploader::failed(FullMsgId itemId) {
+ const auto i = ranges::find(_queue, itemId, &Entry::itemId);
+ if (i != end(_queue)) {
+ const auto entry = std::move(*i);
+ _queue.erase(i);
+ notifyFailed(entry);
}
-
- cancelRequests();
- dcMap.clear();
- uploadingId = FullMsgId();
- sentSize = 0;
- for (int i = 0; i < UploadSessionsCount(); ++i) {
- sentSizes[i] = 0;
- }
-
- sendNext();
+ cancelRequests(itemId);
+ maybeFinishFront();
+ crl::on_main(this, [=] {
+ maybeSend();
+ });
}
-void Uploader::notifyFailed(FullMsgId id, const File &file) {
- const auto type = file.type();
+void Uploader::notifyFailed(const Entry &entry) {
+ const auto type = entry.file->type;
if (type == SendMediaType::Photo) {
- _photoFailed.fire_copy(id);
+ _photoFailed.fire_copy(entry.itemId);
} else if (type == SendMediaType::File
|| type == SendMediaType::ThemeFile
|| type == SendMediaType::Audio) {
- const auto document = session().data().document(file.id());
+ const auto document = session().data().document(entry.file->id);
if (document->uploading()) {
document->status = FileUploadFailed;
}
- _documentFailed.fire_copy(id);
+ _documentFailed.fire_copy(entry.itemId);
} else if (type == SendMediaType::Secure) {
- _secureFailed.fire_copy(id);
+ _secureFailed.fire_copy(entry.itemId);
} else {
- Unexpected("Type in Uploader::currentFailed.");
+ Unexpected("Type in Uploader::failed.");
}
}
void Uploader::stopSessions() {
- for (int i = 0; i < UploadSessionsCount(); ++i) {
- _api->instance().stopSession(MTP::uploadDcId(i));
+ if (ranges::any_of(_sentPerDcIndex, rpl::mappers::_1 != 0)) {
+ _stopSessionsTimer.callOnce(kKillSessionTimeout);
+ } else {
+ for (auto i = 0; i != int(_sentPerDcIndex.size()); ++i) {
+ _api->instance().stopSession(MTP::uploadDcId(i));
+ }
+ _sentPerDcIndex.clear();
+ _dcIndicesWithFastRequests.clear();
}
}
-void Uploader::sendNext() {
- if (sentSize >= UploadSessionsCount() * 512 * 1024 || _pausedId.msg) {
- return;
+QByteArray Uploader::readDocPart(not_null entry) {
+ const auto checked = [&](QByteArray result) {
+ if ((entry->file->type == SendMediaType::File
+ || entry->file->type == SendMediaType::ThemeFile
+ || entry->file->type == SendMediaType::Audio)
+ && entry->docSize <= kUseBigFilesFrom) {
+ entry->md5Hash.feed(result.data(), result.size());
+ }
+ if (result.isEmpty()
+ || (result.size() > entry->docPartSize)
+ || ((result.size() < entry->docPartSize
+ && entry->docPartsSent + 1 != entry->docPartsCount))) {
+ return QByteArray();
+ }
+ return result;
+ };
+ auto &content = entry->file->content;
+ if (!content.isEmpty()) {
+ const auto offset = entry->docPartsSent * entry->docPartSize;
+ return checked(content.mid(offset, entry->docPartSize));
+ } else if (!entry->docFile) {
+ const auto filepath = entry->file->filepath;
+ entry->docFile = std::make_unique(filepath);
+ if (!entry->docFile->open(QIODevice::ReadOnly)) {
+ return QByteArray();
+ }
+ }
+ return checked(entry->docFile->read(entry->docPartSize));
+}
+
+bool Uploader::canAddDcIndex() const {
+ const auto count = int(_sentPerDcIndex.size());
+ return (count < kMaxSessionsCount)
+ && (count == int(_dcIndicesWithFastRequests.size()));
+}
+
+std::optional Uploader::chooseDcIndexForNextRequest(
+ const base::flat_set &used) {
+ for (auto i = 0, count = int(_sentPerDcIndex.size()); i != count; ++i) {
+ if (!_sentPerDcIndex[i] && !used.contains(i)) {
+ return i;
+ }
+ }
+ if (canAddDcIndex()) {
+ const auto result = int(_sentPerDcIndex.size());
+ _sentPerDcIndex.push_back(0);
+ _dcIndicesWithFastRequests.clear();
+ _latestDcIndexAdded = crl::now();
+
+ DEBUG_LOG(("Uploader: Added dc index %1.").arg(result));
+ return result;
+ }
+ auto result = std::optional();
+ for (auto i = 0, count = int(_sentPerDcIndex.size()); i != count; ++i) {
+ if (!used.contains(i)
+ && (!result.has_value()
+ || _sentPerDcIndex[i] < _sentPerDcIndex[*result])) {
+ result = i;
+ }
+ }
+ return result;
+}
+
+Uploader::Entry *Uploader::chooseEntryForNextRequest() {
+ if (!_pendingFromRemovedDcIndices.empty()) {
+ const auto itemId = _pendingFromRemovedDcIndices.front().itemId;
+ const auto i = ranges::find(_queue, itemId, &Entry::itemId);
+ Assert(i != end(_queue));
+ return &*i;
}
+ for (auto i = begin(_queue); i != end(_queue); ++i) {
+ if (i->partsSent < i->parts->size()
+ || i->docPartsSent < i->docPartsCount) {
+ return &*i;
+ }
+ }
+ return nullptr;
+}
+
+auto Uploader::sendPart(not_null entry, uchar dcIndex)
+-> SendResult {
+ return !_pendingFromRemovedDcIndices.empty()
+ ? sendPendingPart(entry, dcIndex)
+ : (entry->partsSent < entry->parts->size())
+ ? sendSlicedPart(entry, dcIndex)
+ : sendDocPart(entry, dcIndex);
+}
+
+template
+void Uploader::sendPreparedRequest(Prepared &&prepared, Request &&request) {
+ auto &sentInSession = _sentPerDcIndex[request.dcIndex];
+ const auto queued = sentInSession;
+ sentInSession += int(request.bytes.size());
+
+ const auto requestId = _api->request(
+ std::move(prepared)
+ ).done([=](const MTPBool &result, mtpRequestId requestId) {
+ partLoaded(result, requestId);
+ }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
+ partFailed(error, requestId);
+ }).toDC(MTP::uploadDcId(request.dcIndex)).send();
+
+ request.sent = crl::now();
+ request.queued = queued;
+ _requests.emplace(requestId, std::move(request));
+}
+
+auto Uploader::sendPendingPart(not_null entry, uchar dcIndex)
+-> SendResult {
+ Expects(!_pendingFromRemovedDcIndices.empty());
+ Expects(_pendingFromRemovedDcIndices.front().itemId == entry->itemId);
+
+ auto request = std::move(_pendingFromRemovedDcIndices.front());
+ _pendingFromRemovedDcIndices.erase(begin(_pendingFromRemovedDcIndices));
+
+ const auto part = request.part;
+ const auto bytes = request.bytes;
+ request.dcIndex = dcIndex;
+ if (request.bigPart) {
+ sendPreparedRequest(MTPupload_SaveBigFilePart(
+ MTP_long(entry->file->id),
+ MTP_int(part),
+ MTP_int(entry->docPartsCount),
+ MTP_bytes(bytes)
+ ), std::move(request));
+ } else {
+ const auto id = request.docPart ? entry->file->id : entry->partsOfId;
+ sendPreparedRequest(MTPupload_SaveFilePart(
+ MTP_long(id),
+ MTP_int(part),
+ MTP_bytes(bytes)
+ ), std::move(request));
+ }
+ return SendResult::Success;
+}
+
+auto Uploader::sendDocPart(not_null entry, uchar dcIndex)
+-> SendResult {
+ const auto itemId = entry->itemId;
+ const auto alreadySent = _sentPerDcIndex[dcIndex];
+ const auto willProbablyBeSent = entry->docPartSize;
+ if (alreadySent + willProbablyBeSent > kMaxUploadPerSession) {
+ return SendResult::DcIndexFull;
+ }
+
+ Assert(entry->docPartsSent < entry->docPartsCount);
+
+ const auto partBytes = readDocPart(entry);
+ if (partBytes.isEmpty()) {
+ failed(itemId);
+ return SendResult::Failed;
+ }
+ const auto part = entry->docPartsSent++;
+ ++entry->docPartsWaiting;
+
+ const auto send = [&](auto &&request, bool big) {
+ sendPreparedRequest(std::move(request), {
+ .itemId = itemId,
+ .bytes = partBytes,
+ .part = part,
+ .dcIndex = dcIndex,
+ .docPart = true,
+ .bigPart = big,
+ });
+ };
+ if (entry->docSize > kUseBigFilesFrom) {
+ send(MTPupload_SaveBigFilePart(
+ MTP_long(entry->file->id),
+ MTP_int(part),
+ MTP_int(entry->docPartsCount),
+ MTP_bytes(partBytes)
+ ), true);
+ } else {
+ send(MTPupload_SaveFilePart(
+ MTP_long(entry->file->id),
+ MTP_int(part),
+ MTP_bytes(partBytes)
+ ), false);
+ }
+ return SendResult::Success;
+}
+
+auto Uploader::sendSlicedPart(not_null entry, uchar dcIndex)
+-> SendResult {
+ const auto itemId = entry->itemId;
+ const auto alreadySent = _sentPerDcIndex[dcIndex];
+ const auto willBeSent = entry->parts->at(entry->partsSent).size();
+ if (alreadySent + willBeSent >= kMaxUploadPerSession) {
+ return SendResult::DcIndexFull;
+ }
+
+ ++entry->partsWaiting;
+ const auto index = entry->partsSent++;
+ const auto partBytes = entry->parts->at(index);
+ sendPreparedRequest(MTPupload_SaveFilePart(
+ MTP_long(entry->partsOfId),
+ MTP_int(index),
+ MTP_bytes(partBytes)
+ ), {
+ .itemId = itemId,
+ .bytes = partBytes,
+ .dcIndex = dcIndex,
+ });
+ return SendResult::Success;
+}
+
+void Uploader::maybeSend() {
const auto stopping = _stopSessionsTimer.isActive();
- if (queue.empty()) {
+ if (_queue.empty()) {
if (!stopping) {
_stopSessionsTimer.callOnce(kKillSessionTimeout);
}
+ _pausedId = FullMsgId();
return;
- }
-
- if (stopping) {
+ } else if (_pausedId) {
+ return;
+ } else if (stopping) {
_stopSessionsTimer.cancel();
}
- auto i = uploadingId.msg ? queue.find(uploadingId) : queue.begin();
- if (!uploadingId.msg) {
- uploadingId = i->first;
- } else if (i == queue.end()) {
- i = queue.begin();
- uploadingId = i->first;
- }
- auto &uploadingData = i->second;
- auto todc = 0;
- for (auto dc = 1; dc != UploadSessionsCount(); ++dc) {
- if (sentSizes[dc] < sentSizes[todc]) {
- todc = dc;
+ auto usedDcIndices = base::flat_set();
+ while (true) {
+ const auto maybeDcIndex = chooseDcIndexForNextRequest(usedDcIndices);
+ if (!maybeDcIndex.has_value()) {
+ break;
+ }
+ const auto dcIndex = *maybeDcIndex;
+ while (true) {
+ const auto entry = chooseEntryForNextRequest();
+ if (!entry) {
+ return;
+ }
+ const auto result = sendPart(entry, dcIndex);
+ if (result == SendResult::DcIndexFull) {
+ return;
+ } else if (result == SendResult::Success) {
+ break;
+ }
+ // If this entry failed, we try the next one.
+ }
+ if (_sentPerDcIndex[dcIndex] >= kAcceptAsFastIfTotalAtLeast) {
+ usedDcIndices.emplace(dcIndex);
}
}
-
- auto &parts = uploadingData.file
- ? ((uploadingData.type() == SendMediaType::Photo
- || uploadingData.type() == SendMediaType::Secure)
- ? uploadingData.file->fileparts
- : uploadingData.file->thumbparts)
- : uploadingData.media.parts;
- const auto partsOfId = uploadingData.file
- ? ((uploadingData.type() == SendMediaType::Photo
- || uploadingData.type() == SendMediaType::Secure)
- ? uploadingData.file->id
- : uploadingData.file->thumbId)
- : uploadingData.media.thumbId;
- if (parts.isEmpty()) {
- if (uploadingData.docSentParts >= uploadingData.docPartsCount) {
- if (requestsSent.empty() && docRequestsSent.empty()) {
- const auto options = uploadingData.file
- ? uploadingData.file->to.options
- : Api::SendOptions();
- const auto edit = uploadingData.file &&
- uploadingData.file->to.replaceMediaOf;
- const auto attachedStickers = uploadingData.file
- ? uploadingData.file->attachedStickers
- : std::vector();
- if (uploadingData.type() == SendMediaType::Photo) {
- auto photoFilename = uploadingData.filename();
- if (!photoFilename.endsWith(u".jpg"_q, Qt::CaseInsensitive)) {
- // Server has some extensions checking for inputMediaUploadedPhoto,
- // so force the extension to be .jpg anyway. It doesn't matter,
- // because the filename from inputFile is not used anywhere.
- photoFilename += u".jpg"_q;
- }
- const auto md5 = uploadingData.file
- ? uploadingData.file->filemd5
- : uploadingData.media.jpeg_md5;
- const auto file = MTP_inputFile(
- MTP_long(uploadingData.id()),
- MTP_int(uploadingData.partsCount),
- MTP_string(photoFilename),
- MTP_bytes(md5));
- _photoReady.fire({
- .fullId = uploadingId,
- .info = {
- .file = file,
- .attachedStickers = attachedStickers,
- },
- .options = options,
- .edit = edit,
- });
- } else if (uploadingData.type() == SendMediaType::File
- || uploadingData.type() == SendMediaType::ThemeFile
- || uploadingData.type() == SendMediaType::Audio) {
- QByteArray docMd5(32, Qt::Uninitialized);
- hashMd5Hex(uploadingData.md5Hash.result(), docMd5.data());
-
- const auto file = (uploadingData.docSize > kUseBigFilesFrom)
- ? MTP_inputFileBig(
- MTP_long(uploadingData.id()),
- MTP_int(uploadingData.docPartsCount),
- MTP_string(uploadingData.filename()))
- : MTP_inputFile(
- MTP_long(uploadingData.id()),
- MTP_int(uploadingData.docPartsCount),
- MTP_string(uploadingData.filename()),
- MTP_bytes(docMd5));
- const auto thumb = [&]() -> std::optional {
- if (!uploadingData.partsCount) {
- return std::nullopt;
- }
- const auto thumbFilename = uploadingData.file
- ? uploadingData.file->thumbname
- : (u"thumb."_q + uploadingData.media.thumbExt);
- const auto thumbMd5 = uploadingData.file
- ? uploadingData.file->thumbmd5
- : uploadingData.media.jpeg_md5;
- return MTP_inputFile(
- MTP_long(uploadingData.thumbId()),
- MTP_int(uploadingData.partsCount),
- MTP_string(thumbFilename),
- MTP_bytes(thumbMd5));
- }();
- _documentReady.fire({
- .fullId = uploadingId,
- .info = {
- .file = file,
- .thumb = thumb,
- .attachedStickers = attachedStickers,
- },
- .options = options,
- .edit = edit,
- });
- } else if (uploadingData.type() == SendMediaType::Secure) {
- _secureReady.fire({
- uploadingId,
- uploadingData.id(),
- uploadingData.partsCount });
- }
- queue.erase(uploadingId);
- uploadingId = FullMsgId();
- sendNext();
- }
- return;
- }
-
- auto &content = uploadingData.file
- ? uploadingData.file->content
- : uploadingData.media.data;
- QByteArray toSend;
- if (content.isEmpty()) {
- if (!uploadingData.docFile) {
- const auto filepath = uploadingData.file
- ? uploadingData.file->filepath
- : uploadingData.media.file;
- uploadingData.docFile = std::make_unique(filepath);
- if (!uploadingData.docFile->open(QIODevice::ReadOnly)) {
- currentFailed();
- return;
- }
- }
- toSend = uploadingData.docFile->read(uploadingData.docPartSize);
- if (uploadingData.docSize <= kUseBigFilesFrom) {
- uploadingData.md5Hash.feed(toSend.constData(), toSend.size());
- }
- } else {
- const auto offset = uploadingData.docSentParts
- * uploadingData.docPartSize;
- toSend = content.mid(offset, uploadingData.docPartSize);
- if ((uploadingData.type() == SendMediaType::File
- || uploadingData.type() == SendMediaType::ThemeFile
- || uploadingData.type() == SendMediaType::Audio)
- && uploadingData.docSentParts <= kUseBigFilesFrom) {
- uploadingData.md5Hash.feed(toSend.constData(), toSend.size());
- }
- }
- if ((toSend.size() > uploadingData.docPartSize)
- || ((toSend.size() < uploadingData.docPartSize
- && uploadingData.docSentParts + 1 != uploadingData.docPartsCount))) {
- currentFailed();
- return;
- }
- mtpRequestId requestId;
- if (uploadingData.docSize > kUseBigFilesFrom) {
- requestId = _api->request(MTPupload_SaveBigFilePart(
- MTP_long(uploadingData.id()),
- MTP_int(uploadingData.docSentParts),
- MTP_int(uploadingData.docPartsCount),
- MTP_bytes(toSend)
- )).done([=](const MTPBool &result, mtpRequestId requestId) {
- partLoaded(result, requestId);
- }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
- partFailed(error, requestId);
- }).toDC(MTP::uploadDcId(todc)).send();
- } else {
- requestId = _api->request(MTPupload_SaveFilePart(
- MTP_long(uploadingData.id()),
- MTP_int(uploadingData.docSentParts),
- MTP_bytes(toSend)
- )).done([=](const MTPBool &result, mtpRequestId requestId) {
- partLoaded(result, requestId);
- }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
- partFailed(error, requestId);
- }).toDC(MTP::uploadDcId(todc)).send();
- }
- docRequestsSent.emplace(requestId, uploadingData.docSentParts);
- dcMap.emplace(requestId, todc);
- sentSize += uploadingData.docPartSize;
- sentSizes[todc] += uploadingData.docPartSize;
-
- uploadingData.docSentParts++;
+ if (usedDcIndices.empty()) {
+ _nextTimer.cancel();
} else {
- auto part = parts.begin();
-
- const auto requestId = _api->request(MTPupload_SaveFilePart(
- MTP_long(partsOfId),
- MTP_int(part.key()),
- MTP_bytes(part.value())
- )).done([=](const MTPBool &result, mtpRequestId requestId) {
- partLoaded(result, requestId);
- }).fail([=](const MTP::Error &error, mtpRequestId requestId) {
- partFailed(error, requestId);
- }).toDC(MTP::uploadDcId(todc)).send();
- requestsSent.emplace(requestId, part.value());
- dcMap.emplace(requestId, todc);
- sentSize += part.value().size();
- sentSizes[todc] += part.value().size();
-
- parts.erase(part);
+ _nextTimer.callOnce(kUploadRequestInterval);
}
- _nextTimer.callOnce(crl::time(UploadSessionsInterval()));
}
-void Uploader::cancel(const FullMsgId &msgId) {
- if (uploadingId == msgId) {
- currentFailed();
- } else {
- queue.erase(msgId);
- }
+void Uploader::cancel(FullMsgId itemId) {
+ failed(itemId);
}
void Uploader::cancelAll() {
- const auto single = queue.empty() ? uploadingId : queue.begin()->first;
- if (!single) {
- return;
- }
- _pausedId = single;
- if (uploadingId) {
- currentFailed();
- }
- while (!queue.empty()) {
- const auto [msgId, file] = std::move(*queue.begin());
- queue.erase(queue.begin());
- notifyFailed(msgId, file);
+ while (!_queue.empty()) {
+ failed(_queue.front().itemId);
}
clear();
unpause();
}
-void Uploader::pause(const FullMsgId &msgId) {
- _pausedId = msgId;
+void Uploader::pause(FullMsgId itemId) {
+ _pausedId = itemId;
}
void Uploader::unpause() {
_pausedId = FullMsgId();
- sendNext();
+ maybeSend();
}
-void Uploader::confirm(const FullMsgId &msgId) {
+void Uploader::cancelRequests(FullMsgId itemId) {
+ for (auto i = begin(_requests); i != end(_requests);) {
+ if (i->second.itemId == itemId) {
+ const auto bytes = int(i->second.bytes.size());
+ _sentPerDcIndex[i->second.dcIndex] -= bytes;
+ _api->request(i->first).cancel();
+ i = _requests.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ _pendingFromRemovedDcIndices.erase(ranges::remove(
+ _pendingFromRemovedDcIndices,
+ itemId,
+ &Request::itemId
+ ), end(_pendingFromRemovedDcIndices));
}
-void Uploader::cancelRequests() {
- for (const auto &requestData : requestsSent) {
- _api->request(requestData.first).cancel();
+void Uploader::cancelAllRequests() {
+ for (const auto &[requestId, request] : base::take(_requests)) {
+ _api->request(requestId).cancel();
}
- requestsSent.clear();
- for (const auto &requestData : docRequestsSent) {
- _api->request(requestData.first).cancel();
- }
- docRequestsSent.clear();
+ ranges::fill(_sentPerDcIndex, 0);
}
void Uploader::clear() {
- queue.clear();
- cancelRequests();
- dcMap.clear();
- sentSize = 0;
- for (int i = 0; i < UploadSessionsCount(); ++i) {
- _api->instance().stopSession(MTP::uploadDcId(i));
- sentSizes[i] = 0;
- }
+ _queue.clear();
+ cancelAllRequests();
+ stopSessions();
_stopSessionsTimer.cancel();
}
-void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
- auto j = docRequestsSent.end();
- auto i = requestsSent.find(requestId);
- if (i == requestsSent.cend()) {
- j = docRequestsSent.find(requestId);
- }
- const auto wasNonPremiumDelayed = _nonPremiumDelayed.remove(requestId);
- if (i != requestsSent.cend() || j != docRequestsSent.cend()) {
- if (mtpIsFalse(result)) { // failed to upload current file
- currentFailed();
- return;
- } else {
- auto dcIt = dcMap.find(requestId);
- if (dcIt == dcMap.cend()) { // must not happen
- currentFailed();
- return;
- }
- auto dc = dcIt->second;
- dcMap.erase(dcIt);
+Uploader::Request Uploader::finishRequest(mtpRequestId requestId) {
+ const auto taken = _requests.take(requestId);
+ Assert(taken.has_value());
- int64 sentPartSize = 0;
- auto k = queue.find(uploadingId);
- Assert(k != queue.cend());
- auto &[fullId, file] = *k;
- if (i != requestsSent.cend()) {
- sentPartSize = i->second.size();
- requestsSent.erase(i);
+ _sentPerDcIndex[taken->dcIndex] -= int(taken->bytes.size());
+ return *taken;
+}
+
+void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
+ const auto request = finishRequest(requestId);
+
+ const auto bytes = int(request.bytes.size());
+ const auto itemId = request.itemId;
+
+ if (mtpIsFalse(result)) { // failed to upload current file
+ failed(itemId);
+ return;
+ }
+
+ const auto i = ranges::find(_queue, itemId, &Entry::itemId);
+ Assert(i != end(_queue));
+ auto &entry = *i;
+
+ const auto now = crl::now();
+ const auto duration = now - request.sent;
+ const auto fast = (duration < kFastRequestThreshold);
+ const auto slowish = !fast;
+ const auto slow = (duration >= kSlowRequestThreshold);
+
+ if (slowish) {
+ _dcIndicesWithFastRequests.clear();
+ if (slow) {
+ const auto elapsed = (now - _latestDcIndexRemoved);
+ const auto remove = (elapsed >= kWaitForNormalizeTimeout);
+ if (remove && _sentPerDcIndex.size() > 1) {
+ DEBUG_LOG(("Uploader: Slow request, removing dc index."));
+ removeDcIndex();
+ _latestDcIndexRemoved = now;
} else {
- sentPartSize = file.docPartSize;
- docRequestsSent.erase(j);
- }
- sentSize -= sentPartSize;
- sentSizes[dc] -= sentPartSize;
- if (file.type() == SendMediaType::Photo) {
- file.fileSentSize += sentPartSize;
- const auto photo = session().data().photo(file.id());
- if (photo->uploading() && file.file) {
- photo->uploadingData->size = file.file->partssize;
- photo->uploadingData->offset = file.fileSentSize;
- }
- _photoProgress.fire_copy(fullId);
- } else if (file.type() == SendMediaType::File
- || file.type() == SendMediaType::ThemeFile
- || file.type() == SendMediaType::Audio) {
- const auto document = session().data().document(file.id());
- if (document->uploading()) {
- const auto doneParts = file.docSentParts
- - int(docRequestsSent.size());
- document->uploadingData->offset = std::min(
- document->uploadingData->size,
- doneParts * file.docPartSize);
- }
- _documentProgress.fire_copy(fullId);
- } else if (file.type() == SendMediaType::Secure) {
- file.fileSentSize += sentPartSize;
- _secureProgress.fire_copy({
- fullId,
- file.fileSentSize,
- file.file->partssize });
- }
- if (wasNonPremiumDelayed) {
- _nonPremiumDelays.fire_copy(fullId);
+ DEBUG_LOG(("Uploader: Slow request, clear fast records."));
}
+ } else {
+ DEBUG_LOG(("Uploader: Slow-ish request, clear fast records."));
+ }
+ } else if (request.sent > _latestDcIndexAdded
+ && (request.queued + bytes >= kAcceptAsFastIfTotalAtLeast)) {
+ if (_dcIndicesWithFastRequests.emplace(request.dcIndex).second) {
+ DEBUG_LOG(("Uploader: Mark %1 of %2 as fast."
+ ).arg(request.dcIndex
+ ).arg(_sentPerDcIndex.size()));
}
}
- sendNext();
+ if (request.docPart) {
+ --entry.docPartsWaiting;
+ entry.docSentSize += bytes;
+ } else {
+ --entry.partsWaiting;
+ entry.sentSize += bytes;
+ }
+
+ if (entry.file->type == SendMediaType::Photo) {
+ const auto photo = session().data().photo(entry.file->id);
+ if (photo->uploading()) {
+ photo->uploadingData->size = entry.file->partssize;
+ photo->uploadingData->offset = entry.sentSize;
+ }
+ _photoProgress.fire_copy(itemId);
+ } else if (entry.file->type == SendMediaType::File
+ || entry.file->type == SendMediaType::ThemeFile
+ || entry.file->type == SendMediaType::Audio) {
+ const auto document = session().data().document(entry.file->id);
+ if (document->uploading()) {
+ document->uploadingData->offset = std::min(
+ document->uploadingData->size,
+ entry.docSentSize);
+ }
+ _documentProgress.fire_copy(itemId);
+ } else if (entry.file->type == SendMediaType::Secure) {
+ _secureProgress.fire_copy({
+ .fullId = itemId,
+ .offset = entry.sentSize,
+ .size = entry.file->partssize,
+ });
+ }
+ if (request.nonPremiumDelayed) {
+ _nonPremiumDelays.fire_copy(itemId);
+ }
+
+ if (!_queue.empty() && itemId == _queue.front().itemId) {
+ maybeFinishFront();
+ }
+ maybeSend();
+}
+
+void Uploader::removeDcIndex() {
+ Expects(_sentPerDcIndex.size() > 1);
+
+ const auto dcIndex = int(_sentPerDcIndex.size()) - 1;
+ for (auto i = begin(_requests); i != end(_requests);) {
+ if (i->second.dcIndex == dcIndex) {
+ const auto bytes = int(i->second.bytes.size());
+ _sentPerDcIndex[dcIndex] -= bytes;
+ _api->request(i->first).cancel();
+ _pendingFromRemovedDcIndices.push_back(std::move(i->second));
+ i = _requests.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ Assert(_sentPerDcIndex.back() == 0);
+ _sentPerDcIndex.pop_back();
+ _dcIndicesWithFastRequests.remove(dcIndex);
+ _api->instance().stopSession(MTP::uploadDcId(dcIndex));
+ DEBUG_LOG(("Uploader: Removed dc index %1.").arg(dcIndex));
+}
+
+void Uploader::maybeFinishFront() {
+ while (!_queue.empty()) {
+ const auto &entry = _queue.front();
+ if (entry.partsSent >= entry.parts->size()
+ && entry.docPartsSent >= entry.docPartsCount
+ && !entry.partsWaiting
+ && !entry.docPartsWaiting) {
+ finishFront();
+ } else {
+ break;
+ }
+ }
+}
+
+void Uploader::finishFront() {
+ Expects(!_queue.empty());
+
+ auto entry = std::move(_queue.front());
+ _queue.erase(_queue.begin());
+
+ const auto options = entry.file
+ ? entry.file->to.options
+ : Api::SendOptions();
+ const auto edit = entry.file &&
+ entry.file->to.replaceMediaOf;
+ const auto attachedStickers = entry.file
+ ? entry.file->attachedStickers
+ : std::vector();
+ if (entry.file->type == SendMediaType::Photo) {
+ auto photoFilename = entry.file->filename;
+ if (!photoFilename.endsWith(u".jpg"_q, Qt::CaseInsensitive)) {
+ // Server has some extensions checking for inputMediaUploadedPhoto,
+ // so force the extension to be .jpg anyway. It doesn't matter,
+ // because the filename from inputFile is not used anywhere.
+ photoFilename += u".jpg"_q;
+ }
+ const auto md5 = entry.file->filemd5;
+ const auto file = MTP_inputFile(
+ MTP_long(entry.file->id),
+ MTP_int(entry.parts->size()),
+ MTP_string(photoFilename),
+ MTP_bytes(md5));
+ _photoReady.fire({
+ .fullId = entry.itemId,
+ .info = {
+ .file = file,
+ .attachedStickers = attachedStickers,
+ },
+ .options = options,
+ .edit = edit,
+ });
+ } else if (entry.file->type == SendMediaType::File
+ || entry.file->type == SendMediaType::ThemeFile
+ || entry.file->type == SendMediaType::Audio) {
+ QByteArray docMd5(32, Qt::Uninitialized);
+ hashMd5Hex(entry.md5Hash.result(), docMd5.data());
+
+ const auto file = (entry.docSize > kUseBigFilesFrom)
+ ? MTP_inputFileBig(
+ MTP_long(entry.file->id),
+ MTP_int(entry.docPartsCount),
+ MTP_string(entry.file->filename))
+ : MTP_inputFile(
+ MTP_long(entry.file->id),
+ MTP_int(entry.docPartsCount),
+ MTP_string(entry.file->filename),
+ MTP_bytes(docMd5));
+ const auto thumb = [&]() -> std::optional {
+ if (entry.parts->empty()) {
+ return std::nullopt;
+ }
+ const auto thumbFilename = entry.file->thumbname;
+ const auto thumbMd5 = entry.file->thumbmd5;
+ return MTP_inputFile(
+ MTP_long(entry.file->thumbId),
+ MTP_int(entry.parts->size()),
+ MTP_string(thumbFilename),
+ MTP_bytes(thumbMd5));
+ }();
+ _documentReady.fire({
+ .fullId = entry.itemId,
+ .info = {
+ .file = file,
+ .thumb = thumb,
+ .attachedStickers = attachedStickers,
+ },
+ .options = options,
+ .edit = edit,
+ });
+ } else if (entry.file->type == SendMediaType::Secure) {
+ _secureReady.fire({
+ entry.itemId,
+ entry.file->id,
+ int(entry.parts->size()),
+ });
+ }
}
void Uploader::partFailed(const MTP::Error &error, mtpRequestId requestId) {
- // failed to upload current file
- _nonPremiumDelayed.remove(requestId);
- if ((requestsSent.find(requestId) != requestsSent.cend())
- || (docRequestsSent.find(requestId) != docRequestsSent.cend())) {
- currentFailed();
- }
- sendNext();
+ const auto request = finishRequest(requestId);
+ failed(request.itemId);
}
} // namespace Storage
diff --git a/Telegram/SourceFiles/storage/file_upload.h b/Telegram/SourceFiles/storage/file_upload.h
index 9f72fbfa5..6f87f6a56 100644
--- a/Telegram/SourceFiles/storage/file_upload.h
+++ b/Telegram/SourceFiles/storage/file_upload.h
@@ -9,11 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_common.h"
#include "base/timer.h"
+#include "base/weak_ptr.h"
#include "mtproto/facade.h"
class ApiWrap;
-struct FileLoadResult;
-struct SendMediaReady;
+struct FilePrepareResult;
namespace Api {
enum class SendProgressType;
@@ -47,54 +47,48 @@ struct UploadSecureDone {
int partsCount = 0;
};
-class Uploader final : public QObject {
+class Uploader final : public base::has_weak_ptr {
public:
explicit Uploader(not_null api);
~Uploader();
[[nodiscard]] Main::Session &session() const;
+ [[nodiscard]] FullMsgId currentUploadId() const;
- [[nodiscard]] FullMsgId currentUploadId() const {
- return uploadingId;
- }
-
- void uploadMedia(const FullMsgId &msgId, const SendMediaReady &image);
void upload(
- const FullMsgId &msgId,
- const std::shared_ptr &file);
-
- void cancel(const FullMsgId &msgId);
- void pause(const FullMsgId &msgId);
- void confirm(const FullMsgId &msgId);
+ FullMsgId itemId,
+ const std::shared_ptr &file);
+ void pause(FullMsgId itemId);
+ void cancel(FullMsgId itemId);
void cancelAll();
- void clear();
- rpl::producer photoReady() const {
+ [[nodiscard]] rpl::producer photoReady() const {
return _photoReady.events();
}
- rpl::producer documentReady() const {
+ [[nodiscard]] rpl::producer documentReady() const {
return _documentReady.events();
}
- rpl::producer secureReady() const {
+ [[nodiscard]] rpl::producer secureReady() const {
return _secureReady.events();
}
- rpl::producer photoProgress() const {
+ [[nodiscard]] rpl::producer photoProgress() const {
return _photoProgress.events();
}
- rpl::producer documentProgress() const {
+ [[nodiscard]] rpl::producer documentProgress() const {
return _documentProgress.events();
}
- rpl::producer secureProgress() const {
+ [[nodiscard]] auto secureProgress() const
+ -> rpl::producer {
return _secureProgress.events();
}
- rpl::producer photoFailed() const {
+ [[nodiscard]] rpl::producer photoFailed() const {
return _photoFailed.events();
}
- rpl::producer documentFailed() const {
+ [[nodiscard]] rpl::producer documentFailed() const {
return _documentFailed.events();
}
- rpl::producer secureFailed() const {
+ [[nodiscard]] rpl::producer secureFailed() const {
return _secureFailed.events();
}
@@ -103,23 +97,53 @@ public:
}
void unpause();
- void sendNext();
void stopSessions();
private:
- struct File;
+ struct Entry;
+ struct Request;
+
+ enum class SendResult : uchar {
+ Success,
+ Failed,
+ DcIndexFull,
+ };
+
+ void maybeSend();
+ [[nodiscard]] bool canAddDcIndex() const;
+ [[nodiscard]] std::optional chooseDcIndexForNextRequest(
+ const base::flat_set &used);
+ [[nodiscard]] Entry *chooseEntryForNextRequest();
+ [[nodiscard]] SendResult sendPart(not_null entry, uchar dcIndex);
+ [[nodiscard]] auto sendPendingPart(not_null entry, uchar dcIndex)
+ -> SendResult;
+ [[nodiscard]] auto sendDocPart(not_null entry, uchar dcIndex)
+ -> SendResult;
+ [[nodiscard]] auto sendSlicedPart(not_null entry, uchar dcIndex)
+ -> SendResult;
+ [[nodiscard]] QByteArray readDocPart(not_null entry);
+ void removeDcIndex();
+
+ template
+ void sendPreparedRequest(Prepared &&prepared, Request &&request);
+
+ void maybeFinishFront();
+ void finishFront();
void partLoaded(const MTPBool &result, mtpRequestId requestId);
void partFailed(const MTP::Error &error, mtpRequestId requestId);
+ Request finishRequest(mtpRequestId requestId);
- void processPhotoProgress(const FullMsgId &msgId);
- void processPhotoFailed(const FullMsgId &msgId);
- void processDocumentProgress(const FullMsgId &msgId);
- void processDocumentFailed(const FullMsgId &msgId);
+ void processPhotoProgress(FullMsgId itemId);
+ void processPhotoFailed(FullMsgId itemId);
+ void processDocumentProgress(FullMsgId itemId);
+ void processDocumentFailed(FullMsgId itemId);
- void notifyFailed(FullMsgId id, const File &file);
- void currentFailed();
- void cancelRequests();
+ void notifyFailed(const Entry &entry);
+ void failed(FullMsgId itemId);
+ void cancelRequests(FullMsgId itemId);
+ void cancelAllRequests();
+ void clear();
void sendProgressUpdate(
not_null item,
@@ -127,16 +151,19 @@ private:
int progress = 0);
const not_null