diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 0497cb6a51..d3bcdcc287 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -1949,7 +1949,8 @@ void GroupCall::applySelfUpdate(const MTPDgroupCallParticipant &data) { } return; } else if (data.vsource().v != _joinState.ssrc) { - if (!_mySsrcs.contains(data.vsource().v)) { + const auto ssrc = uint32(data.vsource().v); + if (!_mySsrcs.contains(ssrc)) { // I joined from another device, hangup. LOG(("Call Info: " "Hangup after '!left' with ssrc %1, my %2." diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 84161be39d..ad7eb523d5 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -13,10 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_changes.h" #include "data/data_user.h" #include "data/data_channel.h" +#include "base/unixtime.h" #include "main/main_session.h" #include "main/main_account.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_message.h" #include "core/application.h" #include "ui/controls/download_bar.h" @@ -30,7 +32,7 @@ constexpr auto ByItem = [](const auto &entry) { return entry.object.item; } else { const auto resolved = entry.object.get(); - return resolved ? resolved->item : nullptr; + return resolved ? resolved->item.get() : nullptr; } }; @@ -87,6 +89,17 @@ void DownloadManager::trackSession(not_null session) { }, data.lifetime); } +int64 DownloadManager::computeNextStarted() { + const auto now = base::unixtime::now(); + if (_lastStartedBase != now) { + _lastStartedBase = now; + _lastStartedAdded = 0; + } else { + ++_lastStartedAdded; + } + return int64(_lastStartedBase) * 1000 + _lastStartedAdded; +} + void DownloadManager::addLoading(DownloadObject object) { Expects(object.item != nullptr); Expects(object.document || object.photo); @@ -109,7 +122,11 @@ void DownloadManager::addLoading(DownloadObject object) { ? object.document->size : object.photo->imageByteSize(PhotoSize::Large); - data.downloading.push_back({ .object = object, .total = size }); + data.downloading.push_back({ + .object = object, + .started = computeNextStarted(), + .total = size, + }); _loading.emplace(item); _loadingProgress = DownloadProgress{ .ready = _loadingProgress.current().ready, @@ -181,6 +198,7 @@ void DownloadManager::addLoaded( .object = std::make_unique(object), }); _loaded.emplace(item); + _loadedAdded.fire(&data.downloaded.back()); const auto i = ranges::find(data.downloading, item, ByItem); if (i != end(data.downloading)) { @@ -208,11 +226,15 @@ void DownloadManager::addLoaded( } auto DownloadManager::loadingList() const --> ranges::any_view { +-> ranges::any_view { return ranges::views::all( _sessions ) | ranges::views::transform([=](const auto &pair) { - return ranges::views::all(pair.second.downloading); + return ranges::views::all( + pair.second.downloading + ) | ranges::views::transform([](const DownloadingId &id) { + return &id; + }); }) | ranges::views::join; } @@ -239,6 +261,31 @@ void DownloadManager::clearLoading() { } } +auto DownloadManager::loadedList() const +-> ranges::any_view { + return ranges::views::all( + _sessions + ) | ranges::views::transform([=](const auto &pair) { + return ranges::views::all( + pair.second.downloaded + ) | ranges::views::filter([](const DownloadedId &id) { + return (id.object != nullptr); + }) | ranges::views::transform([](const DownloadedId &id) { + return &id; + }); + }) | ranges::views::join; +} + +auto DownloadManager::loadedAdded() const +-> rpl::producer> { + return _loadedAdded.events(); +} + +auto DownloadManager::loadedRemoved() const +-> rpl::producer> { + return _loadedRemoved.events(); +} + void DownloadManager::remove( SessionData &data, std::vector::iterator i) { @@ -271,16 +318,14 @@ void DownloadManager::cancel( void DownloadManager::changed(not_null item) { if (_loaded.contains(item)) { auto &data = sessionData(item); - const auto i = ranges::find(data.downloaded, item, ByItem); + const auto i = ranges::find(data.downloaded, item.get(), ByItem); Assert(i != end(data.downloaded)); const auto media = item->media(); const auto photo = media ? media->photo() : nullptr; const auto document = media ? media->document() : nullptr; if (i->object->photo != photo || i->object->document != document) { - *i->object = DownloadObject(); - - _loaded.remove(item); + detach(*i); } } if (_loading.contains(item) || _loadingDone.contains(item)) { @@ -291,11 +336,9 @@ void DownloadManager::changed(not_null item) { void DownloadManager::removed(not_null item) { if (_loaded.contains(item)) { auto &data = sessionData(item); - const auto i = ranges::find(data.downloaded, item, ByItem); + const auto i = ranges::find(data.downloaded, item.get(), ByItem); Assert(i != end(data.downloaded)); - *i->object = DownloadObject(); - - _loaded.remove(item); + detach(*i); } if (_loading.contains(item) || _loadingDone.contains(item)) { auto &data = sessionData(item); @@ -310,6 +353,62 @@ void DownloadManager::removed(not_null item) { } } +not_null DownloadManager::generateItem( + const DownloadObject &object) { + Expects(object.document || object.photo); + + const auto session = object.document + ? &object.document->session() + : &object.photo->session(); + const auto fromId = object.item + ? object.item->from()->id + : session->userPeerId(); + const auto history = object.item + ? object.item->history() + : session->data().history(session->user()); + const auto flags = MessageFlag::FakeHistoryItem; + const auto replyTo = MsgId(); + const auto viaBotId = UserId(); + const auto groupedId = uint64(); + const auto date = base::unixtime::now(); + const auto postAuthor = QString(); + const auto caption = TextWithEntities(); + const auto make = [&](const auto media) { + return history->makeMessage( + history->nextNonHistoryEntryId(), + flags, + replyTo, + viaBotId, + date, + fromId, + QString(), + media, + caption, + HistoryMessageMarkupData()); + }; + const auto result = object.document + ? make(object.document) + : make(object.photo); + _generated.emplace(result); + return result; +} + +void DownloadManager::detach(DownloadedId &id) { + Expects(id.object != nullptr); + Expects(_loaded.contains(id.object->item)); + Expects(!_generated.contains(id.object->item)); + + // Maybe generate new document? + const auto was = id.object->item; + const auto now = generateItem(*id.object); + _loaded.remove(was); + _loaded.emplace(now); + id.object->item = now; + + _loadedRemoved.fire_copy(was); + _loadedAdded.fire_copy(&id); +} + DownloadManager::SessionData &DownloadManager::sessionData( not_null session) { const auto i = _sessions.find(session); @@ -357,13 +456,13 @@ rpl::producer MakeDownloadBarContent() { manager.loadingListChanges() | rpl::to_empty ) | rpl::map([=, &manager] { auto result = Ui::DownloadBarContent(); - for (const auto &id : manager.loadingList()) { + for (const auto id : manager.loadingList()) { if (result.singleName.isEmpty()) { - result.singleName = id.object.document->filename(); + result.singleName = id->object.document->filename(); result.singleThumbnail = QImage(); } ++result.count; - if (id.done) { + if (id->done) { ++result.done; } } diff --git a/Telegram/SourceFiles/data/data_download_manager.h b/Telegram/SourceFiles/data/data_download_manager.h index 08fdd76466..04b0e8e5d9 100644 --- a/Telegram/SourceFiles/data/data_download_manager.h +++ b/Telegram/SourceFiles/data/data_download_manager.h @@ -44,7 +44,7 @@ inline constexpr bool operator==( } struct DownloadObject { - HistoryItem *item = nullptr; + not_null item; DocumentData *document = nullptr; PhotoData *photo = nullptr; }; @@ -82,12 +82,19 @@ public: DownloadDate started); [[nodiscard]] auto loadingList() const - -> ranges::any_view; + -> ranges::any_view; [[nodiscard]] DownloadProgress loadingProgress() const; [[nodiscard]] rpl::producer<> loadingListChanges() const; [[nodiscard]] auto loadingProgressValue() const -> rpl::producer; + [[nodiscard]] auto loadedList() const + -> ranges::any_view; + [[nodiscard]] auto loadedAdded() const + -> rpl::producer>; + [[nodiscard]] auto loadedRemoved() const + -> rpl::producer>; + private: struct SessionData { std::vector downloaded; @@ -98,6 +105,7 @@ private: void check(not_null item); void changed(not_null item); void removed(not_null item); + void detach(DownloadedId &id); void untrack(not_null session); void remove( SessionData &data, @@ -107,6 +115,10 @@ private: std::vector::iterator i); void clearLoading(); + [[nodiscard]] int64 computeNextStarted(); + [[nodiscard]] not_null generateItem( + const DownloadObject &object); + [[nodiscard]] SessionData &sessionData(not_null session); [[nodiscard]] SessionData &sessionData( not_null item); @@ -115,10 +127,17 @@ private: base::flat_set> _loading; base::flat_set> _loadingDone; base::flat_set> _loaded; + base::flat_set> _generated; + + TimeId _lastStartedBase = 0; + int _lastStartedAdded = 0; rpl::event_stream<> _loadingListChanges; rpl::variable _loadingProgress; + rpl::event_stream> _loadedAdded; + rpl::event_stream> _loadedRemoved; + base::Timer _clearLoadingTimer; }; diff --git a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp index 3295a1e107..d657466822 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_widget.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_widget.cpp @@ -434,9 +434,9 @@ void Widget::setupDownloadBar() { ) | rpl::start_with_next([=] { auto &&list = Core::App().downloadManager().loadingList(); auto first = (HistoryItem*)nullptr; - for (const auto &id : list) { + for (const auto id : list) { if (!first) { - first = id.object.item; + first = id->object.item; } else { controller()->showSection( Info::Downloads::Make( diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp index a4a1111143..aba5e489ea 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp @@ -95,33 +95,111 @@ void Provider::refreshViewer() { manager.loadingListChanges() | rpl::to_empty ) | rpl::start_with_next([=, &manager] { auto copy = _downloading; - auto added = false; - for (const auto &id : manager.loadingList()) { - if (!id.done) { - const auto item = id.object.item; - if (!copy.remove(item)) { - added = true; + for (const auto id : manager.loadingList()) { + if (!id->done) { + const auto item = id->object.item; + if (!copy.remove(item) && !_downloaded.contains(item)) { _downloading.emplace(item); - _elements.push_back(Element{ item, id.started }); + _elements.push_back(Element{ item, id->started }); trackItemSession(item); + refreshPostponed(true); } } } for (const auto &item : copy) { - _downloading.remove(item); - // #TODO downloads check if downloaded - _elements.erase( - ranges::remove(_elements, item, &Element::item), - end(_elements)); + Assert(!_downloaded.contains(item)); + remove(item); } - _fullCount = _elements.size(); - if (added) { - ranges::sort(_elements, ranges::less(), &Element::started); - _refreshed.fire({}); - } else if (!copy.empty()) { - _refreshed.fire({}); + if (!_fullCount.has_value()) { + refreshPostponed(false); } }, _lifetime); + + for (const auto id : manager.loadedList()) { + addPostponed(id); + } + + manager.loadedAdded( + ) | rpl::start_with_next([=](not_null entry) { + addPostponed(entry); + }, _lifetime); + + manager.loadedRemoved( + ) | rpl::start_with_next([=](not_null item) { + if (!_downloading.contains(item)) { + remove(item); + } else { + _downloaded.remove(item); + _addPostponed.remove(item); + } + }, _lifetime); + + performAdd(); + performRefresh(); +} + +void Provider::addPostponed(not_null entry) { + Expects(entry->object != nullptr); + + const auto item = entry->object->item; + trackItemSession(item); + if (_addPostponed.emplace(item, entry->started).second + && _addPostponed.size() == 1) { + Ui::PostponeCall(this, [=] { + performAdd(); + }); + } +} + +void Provider::performAdd() { + if (_addPostponed.empty()) { + return; + } + for (const auto &[item, started] : base::take(_addPostponed)) { + _downloaded.emplace(item); + if (!_downloading.remove(item)) { + _elements.push_back(Element{ item, started }); + } + } + refreshPostponed(true); +} + +void Provider::remove(not_null item) { + _addPostponed.remove(item); + _downloading.remove(item); + _downloaded.remove(item); + _elements.erase( + ranges::remove(_elements, item, &Element::item), + end(_elements)); + if (const auto i = _layouts.find(item); i != end(_layouts)) { + _layoutRemoved.fire(i->second.item.get()); + _layouts.erase(i); + } + refreshPostponed(false); +} + +void Provider::refreshPostponed(bool added) { + if (added) { + _postponedRefreshSort = true; + } + if (!_postponedRefresh) { + _postponedRefresh = true; + Ui::PostponeCall(this, [=] { + performRefresh(); + }); + } +} + +void Provider::performRefresh() { + if (!_postponedRefresh) { + return; + } + _postponedRefresh = false; + _fullCount = _elements.size(); + if (base::take(_postponedRefreshSort)) { + ranges::sort(_elements, ranges::greater(), &Element::started); + } + _refreshed.fire({}); } void Provider::trackItemSession(not_null item) { @@ -213,10 +291,7 @@ bool Provider::isAfter( } void Provider::itemRemoved(not_null item) { - if (const auto i = _layouts.find(item); i != end(_layouts)) { - _layoutRemoved.fire(i->second.item.get()); - _layouts.erase(i); - } + remove(item); } BaseLayout *Provider::getLayout( diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.h b/Telegram/SourceFiles/info/downloads/info_downloads_provider.h index 777db1be96..f1bdf8471a 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.h +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.h @@ -8,6 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "info/media/info_media_common.h" +#include "base/weak_ptr.h" + +namespace Data { +struct DownloadedId; +} // namespace Data namespace Info { class AbstractController; @@ -17,7 +22,8 @@ namespace Info::Downloads { class Provider final : public Media::ListProvider - , private Media::ListSectionDelegate { + , private Media::ListSectionDelegate + , public base::has_weak_ptr { public: explicit Provider(not_null controller); @@ -77,6 +83,11 @@ private: void markLayoutsStale(); void clearStaleLayouts(); + void refreshPostponed(bool added); + void addPostponed(not_null entry); + void performRefresh(); + void performAdd(); + void remove(not_null item); void trackItemSession(not_null item); [[nodiscard]] Media::BaseLayout *getLayout( @@ -93,6 +104,8 @@ private: base::flat_set> _downloading; base::flat_set> _downloaded; + base::flat_map, int64> _addPostponed; + std::unordered_map< not_null, Media::CachedItem> _layouts; @@ -100,6 +113,8 @@ private: rpl::event_stream<> _refreshed; base::flat_map, rpl::lifetime> _trackedSessions; + bool _postponedRefreshSort = false; + bool _postponedRefresh = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/info/info_controller.cpp b/Telegram/SourceFiles/info/info_controller.cpp index 2a506e39d6..0caaf17f02 100644 --- a/Telegram/SourceFiles/info/info_controller.cpp +++ b/Telegram/SourceFiles/info/info_controller.cpp @@ -108,28 +108,6 @@ rpl::producer AbstractController::mediaSourceQueryValue() const { return rpl::single(QString()); } -rpl::producer AbstractController::downloadsSource() const { - const auto manager = &Core::App().downloadManager(); - return rpl::single( - rpl::empty_value() - ) | rpl::then( - manager->loadingListChanges() - ) | rpl::map([=] { - auto result = DownloadsSlice(); - for (const auto &id : manager->loadingList()) { - result.entries.push_back(DownloadsEntry{ - .item = id.object.item, - .started = id.started, - }); - } - ranges::sort( - result.entries, - ranges::less(), - &DownloadsEntry::started); - return result; - }); -} - AbstractController::AbstractController( not_null parent) : SessionNavigation(&parent->session()) diff --git a/Telegram/SourceFiles/info/info_controller.h b/Telegram/SourceFiles/info/info_controller.h index 82978e5d1c..1656a5c78b 100644 --- a/Telegram/SourceFiles/info/info_controller.h +++ b/Telegram/SourceFiles/info/info_controller.h @@ -113,14 +113,6 @@ private: }; -struct DownloadsEntry { - not_null item; - int64 started = 0; // unixtime * 1000. -}; -struct DownloadsSlice { - std::vector entries; -}; - class AbstractController : public Window::SessionNavigation { public: AbstractController(not_null parent); @@ -150,8 +142,6 @@ public: int limitAfter) const; virtual rpl::producer mediaSourceQueryValue() const; - [[nodiscard]] rpl::producer downloadsSource() const; - void showSection( std::shared_ptr memento, const Window::SectionShow ¶ms = Window::SectionShow()) override; diff --git a/Telegram/SourceFiles/info/media/info_media_list_section.cpp b/Telegram/SourceFiles/info/media/info_media_list_section.cpp index 4f1d9e9648..03ecdb6f53 100644 --- a/Telegram/SourceFiles/info/media/info_media_list_section.cpp +++ b/Telegram/SourceFiles/info/media/info_media_list_section.cpp @@ -243,8 +243,6 @@ void ListSection::paint( outerWidth); } auto localContext = context.layoutContext; - localContext.isAfterDate = (header > 0); - if (!_mosaic.empty()) { auto paintItem = [&](not_null item, QPoint point) { p.translate(point.x(), point.y()); @@ -266,8 +264,7 @@ void ListSection::paint( for (auto it = fromIt; it != tillIt; ++it) { auto item = *it; auto rect = findItemRect(item); - localContext.isAfterDate = (header > 0) - && (rect.y() <= header + _itemsTop); + localContext.skipBorder = (rect.y() <= header + _itemsTop); if (rect.intersects(clip)) { p.translate(rect.topLeft()); item->paint( diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 069501369b..58506b6432 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -1090,7 +1090,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con datetop = st::linksBorder + _st.fileDateTop; QRect border(style::rtlrect(nameleft, 0, _width - nameleft, st::linksBorder, _width)); - if (!context->isAfterDate && clip.intersects(border)) { + if (!context->skipBorder && clip.intersects(border)) { p.fillRect(clip.intersected(border), st::linksBorderFg); } @@ -1661,7 +1661,7 @@ void Link::paint(Painter &p, const QRect &clip, TextSelection selection, const P } QRect border(style::rtlrect(left, 0, w, st::linksBorder, _width)); - if (!context->isAfterDate && clip.intersects(border)) { + if (!context->skipBorder && clip.intersects(border)) { p.fillRect(clip.intersected(border), st::linksBorderFg); } diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index 6d61d2124b..c438c9d386 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -38,7 +38,7 @@ class PaintContext : public PaintContextBase { public: PaintContext(crl::time ms, bool selecting) : PaintContextBase(ms, selecting) { } - bool isAfterDate = false; + bool skipBorder = false; }; diff --git a/Telegram/lib_base b/Telegram/lib_base index 1ec9a06352..3827b6186e 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 1ec9a0635231a37e18fbe6291a58c685cdf75d5b +Subproject commit 3827b6186eab1e6f7ca335d88310bd9e3f6fe440