Support "Delete all files" menu in Downloads section.

This commit is contained in:
John Preston 2022-03-10 18:02:38 +04:00
parent 32d09f189b
commit 1bc438ed01
7 changed files with 163 additions and 15 deletions

View File

@ -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.";

View File

@ -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<not_null<Main::Session*>> sessions;
base::flat_map<QString, DocumentDescriptor> 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<GlobalMsgId> &ids) {
struct DocumentDescriptor {
uint64 sessionUniqueId = 0;
DocumentId documentId = 0;
FullMsgId itemId;
};
auto sessions = base::flat_set<not_null<Main::Session*>>();
auto files = base::flat_map<QString, DocumentDescriptor>();
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<GlobalMsgId> &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<GlobalMsgId> &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<GlobalMsgId> &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<const DownloadingId*, ranges::category::input> {
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<Main::Session*> 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<Main::Session*> session,
DownloadedId &id) {

View File

@ -93,6 +93,8 @@ public:
void clearIfFinished();
void deleteFiles(const std::vector<GlobalMsgId> &ids);
void deleteAll();
[[nodiscard]] bool loadedHasNonCloudFile() const;
[[nodiscard]] auto loadingList() const
-> ranges::any_view<const DownloadingId*, ranges::category::input>;
@ -113,8 +115,10 @@ public:
-> rpl::producer<not_null<const DownloadedId*>>;
[[nodiscard]] auto loadedRemoved() const
-> rpl::producer<not_null<const HistoryItem*>>;
[[nodiscard]] rpl::producer<> loadedResolveDone() const;
private:
struct DeleteFilesDescriptor;
struct SessionData {
std::vector<DownloadedId> downloaded;
std::vector<DownloadingId> downloading;
@ -152,6 +156,8 @@ private:
void resolveRequestsFinished(
not_null<Main::Session*> session,
SessionData &data);
void checkFullResolveDone();
[[nodiscard]] not_null<HistoryItem*> regenerateItem(
const DownloadObject &previous);
[[nodiscard]] not_null<HistoryItem*> generateFakeItem(
@ -166,6 +172,7 @@ private:
Main::Session *onlyInSession) const;
void loadingStop(Main::Session *onlyInSession);
void finishFilesDelete(DeleteFilesDescriptor &&descriptor);
void writePostponed(not_null<Main::Session*> session);
[[nodiscard]] Fn<std::optional<QByteArray>()> serializator(
not_null<Main::Session*> session) const;
@ -188,6 +195,7 @@ private:
rpl::event_stream<not_null<const DownloadedId*>> _loadedAdded;
rpl::event_stream<not_null<const HistoryItem*>> _loadedRemoved;
rpl::variable<bool> _loadedResolveDone;
base::Timer _clearLoadingTimer;

View File

@ -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);
}

View File

@ -130,6 +130,7 @@ private:
base::flat_map<not_null<Main::Session*>, rpl::lifetime> _trackedSessions;
bool _postponedRefreshSort = false;
bool _postponedRefresh = false;
bool _started = false;
rpl::lifetime _lifetime;

View File

@ -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<void()> 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;

View File

@ -197,6 +197,7 @@ private:
void addTopBarMenuButton();
void addProfileCallsButton();
void showTopBarMenu();
void deleteAllDownloads();
rpl::variable<Wrap> _wrap;
std::unique_ptr<Controller> _controller;