diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 1ff74a9430..d3fc4378f2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1845,6 +1845,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_downloads_view_in_chat" = "View in chat"; "lng_downloads_view_in_section" = "View in downloads"; "lng_downloads_delete_sure_one" = "Do you want to delete this file?"; +"lng_downloads_delete_sure_all" = "Do you want to delete all files?"; "lng_downloads_delete_sure#one" = "Do you want to delete {count} file?"; "lng_downloads_delete_sure#other" = "Do you want to delete {count} files?"; "lng_downloads_delete_in_cloud_one" = "It will be deleted from your disk, but will remain accessible in the cloud."; diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index 86c0f8f4bf..a127f60b87 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -63,8 +63,19 @@ constexpr auto ByDocument = [](const auto &entry) { return 0; } +struct DocumentDescriptor { + uint64 sessionUniqueId = 0; + DocumentId documentId = 0; + FullMsgId itemId; +}; + } // namespace +struct DownloadManager::DeleteFilesDescriptor { + base::flat_set> sessions; + base::flat_map files; +}; + DownloadManager::DownloadManager() : _clearLoadingTimer([=] { clearLoading(); }) { } @@ -118,7 +129,8 @@ void DownloadManager::itemVisibilitiesUpdated( return; } for (const auto &id : i->second.downloading) { - if (!session->data().queryItemVisibility(id.object.item)) { + if (!id.done + && !session->data().queryItemVisibility(id.object.item)) { for (auto &id : i->second.downloading) { id.hiddenByView = false; } @@ -308,13 +320,7 @@ void DownloadManager::clearIfFinished() { } void DownloadManager::deleteFiles(const std::vector &ids) { - struct DocumentDescriptor { - uint64 sessionUniqueId = 0; - DocumentId documentId = 0; - FullMsgId itemId; - }; - auto sessions = base::flat_set>(); - auto files = base::flat_map(); + auto descriptor = DeleteFilesDescriptor(); for (const auto &id : ids) { if (const auto item = MessageByGlobalId(id)) { const auto session = &item->history()->session(); @@ -334,7 +340,7 @@ void DownloadManager::deleteFiles(const std::vector &ids) { const auto k = ranges::find(data.downloaded, item, ByItem); if (k != end(data.downloaded)) { const auto document = k->object->document; - files.emplace(k->path, DocumentDescriptor{ + descriptor.files.emplace(k->path, DocumentDescriptor{ .sessionUniqueId = id.sessionUniqueId, .documentId = document ? document->id : DocumentId(), .itemId = id.itemId, @@ -347,14 +353,54 @@ void DownloadManager::deleteFiles(const std::vector &ids) { data.downloaded.erase(k); _loadedRemoved.fire_copy(item); - sessions.emplace(session); + descriptor.sessions.emplace(session); } } } - for (const auto &session : sessions) { + finishFilesDelete(std::move(descriptor)); +} + +void DownloadManager::deleteAll() { + auto descriptor = DeleteFilesDescriptor(); + for (auto &[session, data] : _sessions) { + if (!data.downloaded.empty()) { + descriptor.sessions.emplace(session); + } else if (data.downloading.empty()) { + continue; + } + const auto sessionUniqueId = session->uniqueId(); + while (!data.downloading.empty()) { + cancel(data, data.downloading.end() - 1); + } + for (auto &id : base::take(data.downloaded)) { + const auto object = id.object.get(); + const auto document = object ? object->document : nullptr; + descriptor.files.emplace(id.path, DocumentDescriptor{ + .sessionUniqueId = sessionUniqueId, + .documentId = document ? document->id : DocumentId(), + .itemId = id.itemId, + }); + if (document) { + _generatedDocuments.remove(document); + } + if (const auto item = object ? object->item.get() : nullptr) { + _loaded.remove(item); + _generated.remove(item); + _loadedRemoved.fire_copy(item); + } + } + } + for (const auto &session : descriptor.sessions) { writePostponed(session); } - crl::async([files = std::move(files)] { + finishFilesDelete(std::move(descriptor)); +} + +void DownloadManager::finishFilesDelete(DeleteFilesDescriptor &&descriptor) { + for (const auto &session : descriptor.sessions) { + writePostponed(session); + } + crl::async([files = std::move(descriptor.files)]{ for (const auto &file : files) { QFile(file.first).remove(); crl::on_main([descriptor = file.second] { @@ -374,6 +420,19 @@ void DownloadManager::deleteFiles(const std::vector &ids) { }); } +bool DownloadManager::loadedHasNonCloudFile() const { + for (const auto &[session, data] : _sessions) { + for (const auto &id : data.downloaded) { + if (const auto object = id.object.get()) { + if (!object->item->isHistoryEntry()) { + return true; + } + } + } + } + return false; +} + auto DownloadManager::loadingList() const -> ranges::any_view { return ranges::views::all( @@ -515,9 +574,17 @@ auto DownloadManager::loadedList() }) | ranges::views::join; } +rpl::producer<> DownloadManager::loadedResolveDone() const { + using namespace rpl::mappers; + return _loadedResolveDone.value() | rpl::filter(_1) | rpl::to_empty; +} + void DownloadManager::resolve( not_null session, SessionData &data) { + const auto guard = gsl::finally([&] { + checkFullResolveDone(); + }); if (data.resolveSentTotal >= data.resolveNeeded || data.resolveSentTotal >= kMaxResolvePerAttempt) { return; @@ -622,6 +689,19 @@ void DownloadManager::resolveRequestsFinished( }); } +void DownloadManager::checkFullResolveDone() { + if (_loadedResolveDone.current()) { + return; + } + for (const auto &[session, data] : _sessions) { + if (data.resolveSentTotal < data.resolveNeeded + || data.resolveSentRequests > 0) { + return; + } + } + _loadedResolveDone = true; +} + void DownloadManager::generateEntry( not_null session, DownloadedId &id) { diff --git a/Telegram/SourceFiles/data/data_download_manager.h b/Telegram/SourceFiles/data/data_download_manager.h index a6611e6a89..e20ac639b4 100644 --- a/Telegram/SourceFiles/data/data_download_manager.h +++ b/Telegram/SourceFiles/data/data_download_manager.h @@ -93,6 +93,8 @@ public: void clearIfFinished(); void deleteFiles(const std::vector &ids); + void deleteAll(); + [[nodiscard]] bool loadedHasNonCloudFile() const; [[nodiscard]] auto loadingList() const -> ranges::any_view; @@ -113,8 +115,10 @@ public: -> rpl::producer>; [[nodiscard]] auto loadedRemoved() const -> rpl::producer>; + [[nodiscard]] rpl::producer<> loadedResolveDone() const; private: + struct DeleteFilesDescriptor; struct SessionData { std::vector downloaded; std::vector downloading; @@ -152,6 +156,8 @@ private: void resolveRequestsFinished( not_null session, SessionData &data); + void checkFullResolveDone(); + [[nodiscard]] not_null regenerateItem( const DownloadObject &previous); [[nodiscard]] not_null generateFakeItem( @@ -166,6 +172,7 @@ private: Main::Session *onlyInSession) const; void loadingStop(Main::Session *onlyInSession); + void finishFilesDelete(DeleteFilesDescriptor &&descriptor); void writePostponed(not_null session); [[nodiscard]] Fn()> serializator( not_null session) const; @@ -188,6 +195,7 @@ private: rpl::event_stream> _loadedAdded; rpl::event_stream> _loadedRemoved; + rpl::variable _loadedResolveDone; base::Timer _clearLoadingTimer; diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp index 978c465826..8fcb3980e3 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.cpp @@ -85,9 +85,10 @@ void Provider::checkPreload( } void Provider::refreshViewer() { - if (_fullCount) { + if (_started) { return; } + _started = true; auto &manager = Core::App().downloadManager(); rpl::single(rpl::empty) | rpl::then( manager.loadingListChanges() | rpl::to_empty @@ -138,6 +139,13 @@ void Provider::refreshViewer() { } }, _lifetime); + manager.loadedResolveDone( + ) | rpl::start_with_next([=] { + if (!_fullCount.has_value()) { + _fullCount = 0; + } + }, _lifetime); + performAdd(); performRefresh(); } @@ -211,7 +219,9 @@ void Provider::performRefresh() { return; } _postponedRefresh = false; - _fullCount = _elements.size(); + if (!_elements.empty() || _fullCount.has_value()) { + _fullCount = _elements.size(); + } if (base::take(_postponedRefreshSort)) { ranges::sort(_elements, ranges::less(), &Element::started); } diff --git a/Telegram/SourceFiles/info/downloads/info_downloads_provider.h b/Telegram/SourceFiles/info/downloads/info_downloads_provider.h index 1c17e46582..51b3c38ede 100644 --- a/Telegram/SourceFiles/info/downloads/info_downloads_provider.h +++ b/Telegram/SourceFiles/info/downloads/info_downloads_provider.h @@ -130,6 +130,7 @@ private: base::flat_map, rpl::lifetime> _trackedSessions; bool _postponedRefreshSort = false; bool _postponedRefresh = false; + bool _started = false; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp index 1aded0a0e3..6972a22426 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.cpp +++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp @@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/boxes/confirm_box.h" #include "main/main_session.h" #include "mtproto/mtproto_config.h" +#include "data/data_download_manager.h" #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_user.h" @@ -397,6 +398,29 @@ void WrapWidget::createTopBar() { && (section.settingsType() == Section::SettingsType::Main || section.settingsType() == Section::SettingsType::Chat)) { addTopBarMenuButton(); + } else if (section.type() == Section::Type::Downloads) { + auto &manager = Core::App().downloadManager(); + rpl::merge( + rpl::single(false), + manager.loadingListChanges() | rpl::map_to(false), + manager.loadedAdded() | rpl::map_to(true), + manager.loadedRemoved() | rpl::map_to(false) + ) | rpl::start_with_next([=, &manager](bool definitelyHas) { + const auto has = [&] { + for (const auto id : manager.loadingList()) { + return true; + } + for (const auto id : manager.loadedList()) { + return true; + } + return false; + }; + if (!definitelyHas && !has()) { + _topBarMenuToggle = nullptr; + } else if (!_topBarMenuToggle) { + addTopBarMenuButton(); + } + }, _topBar->lifetime()); } _topBar->lower(); @@ -495,7 +519,12 @@ void WrapWidget::showTopBarMenu() { const style::icon *icon) { return _topBarMenu->addAction(text, std::move(callback), icon); }; - if (const auto peer = key().peer()) { + if (key().isDownloads()) { + addAction( + tr::lng_context_delete_all_files(tr::now), + [=] { deleteAllDownloads(); }, + &st::menuIconDelete); + } else if (const auto peer = key().peer()) { Window::FillDialogsEntryMenu( _controller->parentController(), Dialogs::EntryState{ @@ -525,6 +554,24 @@ void WrapWidget::showTopBarMenu() { _topBarMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight); } +void WrapWidget::deleteAllDownloads() { + auto &manager = Core::App().downloadManager(); + const auto phrase = tr::lng_downloads_delete_sure_all(tr::now); + const auto added = manager.loadedHasNonCloudFile() + ? QString() + : tr::lng_downloads_delete_in_cloud(tr::now); + const auto deleteSure = [=, &manager](Fn close) { + Ui::PostponeCall(this, close); + manager.deleteAll(); + }; + _controller->parentController()->show(Ui::MakeConfirmBox({ + .text = phrase + (added.isEmpty() ? QString() : "\n\n" + added), + .confirmed = deleteSure, + .confirmText = tr::lng_box_delete(tr::now), + .confirmStyle = &st::attentionBoxButton, + })); +} + bool WrapWidget::requireTopBarSearch() const { if (!_controller->searchFieldController()) { return false; diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h index 37e23012e7..37b12b6e75 100644 --- a/Telegram/SourceFiles/info/info_wrap_widget.h +++ b/Telegram/SourceFiles/info/info_wrap_widget.h @@ -197,6 +197,7 @@ private: void addTopBarMenuButton(); void addProfileCallsButton(); void showTopBarMenu(); + void deleteAllDownloads(); rpl::variable _wrap; std::unique_ptr _controller;