mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-08-31 14:38:15 +00:00
Use Data::DocumentMedia to store good thumbnails.
This commit is contained in:
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_streaming.h"
|
||||
#include "data/data_document_good_thumbnail.h"
|
||||
#include "data/data_document_media.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "main/main_session.h"
|
||||
@@ -569,7 +570,6 @@ void DocumentData::setattributes(
|
||||
_additional = nullptr;
|
||||
}
|
||||
}
|
||||
validateGoodThumbnail();
|
||||
if (isAudioFile() || isAnimation() || isVoiceMessage()) {
|
||||
setMaybeSupportsStreaming(true);
|
||||
}
|
||||
@@ -612,7 +612,6 @@ bool DocumentData::checkWallPaperProperties() {
|
||||
return false; // #TODO themes support svg patterns
|
||||
}
|
||||
type = WallPaperDocument;
|
||||
validateGoodThumbnail();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -661,48 +660,65 @@ Storage::Cache::Key DocumentData::goodThumbnailCacheKey() const {
|
||||
return Data::DocumentThumbCacheKey(_dc, id);
|
||||
}
|
||||
|
||||
Image *DocumentData::goodThumbnail() const {
|
||||
return _goodThumbnail.get();
|
||||
bool DocumentData::goodThumbnailChecked() const {
|
||||
return (_goodThumbnailState & GoodThumbnailFlag::Mask)
|
||||
== GoodThumbnailFlag::Checked;
|
||||
}
|
||||
|
||||
void DocumentData::validateGoodThumbnail() {
|
||||
if (!isVideoFile()
|
||||
&& !isAnimation()
|
||||
&& !isWallPaper()
|
||||
&& !isTheme()
|
||||
&& (!sticker() || !sticker()->animated)) {
|
||||
_goodThumbnail = nullptr;
|
||||
} else if (!_goodThumbnail && hasRemoteLocation()) {
|
||||
_goodThumbnail = std::make_unique<Image>(
|
||||
std::make_unique<Data::GoodThumbSource>(this));
|
||||
}
|
||||
bool DocumentData::goodThumbnailGenerating() const {
|
||||
return (_goodThumbnailState & GoodThumbnailFlag::Mask)
|
||||
== GoodThumbnailFlag::Generating;
|
||||
}
|
||||
|
||||
void DocumentData::refreshGoodThumbnail() {
|
||||
if (_goodThumbnail && hasRemoteLocation()) {
|
||||
replaceGoodThumbnail(std::make_unique<Data::GoodThumbSource>(this));
|
||||
}
|
||||
bool DocumentData::goodThumbnailNoData() const {
|
||||
return (_goodThumbnailState & GoodThumbnailFlag::Mask)
|
||||
== GoodThumbnailFlag::NoData;
|
||||
}
|
||||
|
||||
void DocumentData::replaceGoodThumbnail(
|
||||
std::unique_ptr<Images::Source> &&source) {
|
||||
_goodThumbnail->replaceSource(std::move(source));
|
||||
void DocumentData::setGoodThumbnailGenerating() {
|
||||
_goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
|
||||
| GoodThumbnailFlag::Generating;
|
||||
}
|
||||
|
||||
void DocumentData::setGoodThumbnailOnUpload(
|
||||
QImage &&image,
|
||||
QByteArray &&bytes) {
|
||||
Expects(uploadingData != nullptr);
|
||||
void DocumentData::setGoodThumbnailDataReady() {
|
||||
_goodThumbnailState = GoodThumbnailFlag::DataReady
|
||||
| (goodThumbnailNoData()
|
||||
? GoodThumbnailFlag(0)
|
||||
: (_goodThumbnailState & GoodThumbnailFlag::Mask));
|
||||
}
|
||||
|
||||
if (image.isNull()) {
|
||||
void DocumentData::setGoodThumbnailChecked(bool hasData) {
|
||||
if (!hasData && (_goodThumbnailState & GoodThumbnailFlag::DataReady)) {
|
||||
_goodThumbnailState &= ~GoodThumbnailFlag::DataReady;
|
||||
_goodThumbnailState &= ~GoodThumbnailFlag::Mask;
|
||||
Data::DocumentMedia::CheckGoodThumbnail(this);
|
||||
return;
|
||||
}
|
||||
_goodThumbnail = std::make_unique<Image>(
|
||||
std::make_unique<Images::LocalFileSource>(
|
||||
QString(),
|
||||
std::move(bytes),
|
||||
sticker() ? "WEBP" : "JPG",
|
||||
std::move(image)));
|
||||
_goodThumbnailState = (_goodThumbnailState & ~GoodThumbnailFlag::Mask)
|
||||
| (hasData
|
||||
? GoodThumbnailFlag::Checked
|
||||
: GoodThumbnailFlag::NoData);
|
||||
}
|
||||
|
||||
std::shared_ptr<Data::DocumentMedia> DocumentData::createMediaView() {
|
||||
if (auto result = activeMediaView()) {
|
||||
return result;
|
||||
}
|
||||
auto result = std::make_shared<Data::DocumentMedia>(this);
|
||||
_media = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Data::DocumentMedia> DocumentData::activeMediaView() {
|
||||
return _media.lock();
|
||||
}
|
||||
|
||||
void DocumentData::setGoodThumbnailPhoto(not_null<PhotoData*> photo) {
|
||||
_goodThumbnailPhoto = photo;
|
||||
}
|
||||
|
||||
PhotoData *DocumentData::goodThumbnailPhoto() const {
|
||||
return _goodThumbnailPhoto;
|
||||
}
|
||||
|
||||
auto DocumentData::bigFileBaseCacheKey() const
|
||||
@@ -817,7 +833,8 @@ bool DocumentData::loaded(FilePathResolve resolve) const {
|
||||
ActiveCache().increment(ComputeUsage(that->sticker()));
|
||||
}
|
||||
|
||||
that->refreshGoodThumbnail();
|
||||
that->setGoodThumbnailDataReady();
|
||||
Data::DocumentMedia::CheckGoodThumbnail(that);
|
||||
destroyLoader();
|
||||
|
||||
if (!that->_data.isEmpty() || that->getStickerLarge()) {
|
||||
@@ -1606,7 +1623,6 @@ void DocumentData::setRemoteLocation(
|
||||
}
|
||||
}
|
||||
}
|
||||
validateGoodThumbnail();
|
||||
}
|
||||
|
||||
void DocumentData::setContentUrl(const QString &url) {
|
||||
|
@@ -32,6 +32,7 @@ class Loader;
|
||||
|
||||
namespace Data {
|
||||
class Session;
|
||||
class DocumentMedia;
|
||||
} // namespace Data
|
||||
|
||||
namespace Main {
|
||||
@@ -180,11 +181,18 @@ public:
|
||||
ImagePtr thumbnailInline,
|
||||
ImagePtr thumbnail);
|
||||
|
||||
[[nodiscard]] Image *goodThumbnail() const;
|
||||
[[nodiscard]] Storage::Cache::Key goodThumbnailCacheKey() const;
|
||||
void setGoodThumbnailOnUpload(QImage &&image, QByteArray &&bytes);
|
||||
void refreshGoodThumbnail();
|
||||
void replaceGoodThumbnail(std::unique_ptr<Images::Source> &&source);
|
||||
[[nodiscard]] bool goodThumbnailChecked() const;
|
||||
[[nodiscard]] bool goodThumbnailGenerating() const;
|
||||
[[nodiscard]] bool goodThumbnailNoData() const;
|
||||
void setGoodThumbnailGenerating();
|
||||
void setGoodThumbnailDataReady();
|
||||
void setGoodThumbnailChecked(bool hasData);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Data::DocumentMedia> createMediaView();
|
||||
[[nodiscard]] std::shared_ptr<Data::DocumentMedia> activeMediaView();
|
||||
void setGoodThumbnailPhoto(not_null<PhotoData*> photo);
|
||||
[[nodiscard]] PhotoData *goodThumbnailPhoto() const;
|
||||
|
||||
[[nodiscard]] auto bigFileBaseCacheKey() const
|
||||
-> std::optional<Storage::Cache::Key>;
|
||||
@@ -258,6 +266,17 @@ private:
|
||||
using Flags = base::flags<Flag>;
|
||||
friend constexpr bool is_flag_type(Flag) { return true; };
|
||||
|
||||
enum class GoodThumbnailFlag : uchar {
|
||||
Checked = 0x01,
|
||||
Generating = 0x02,
|
||||
NoData = 0x03,
|
||||
Mask = 0x03,
|
||||
|
||||
DataReady = 0x04,
|
||||
};
|
||||
using GoodThumbnailState = base::flags<GoodThumbnailFlag>;
|
||||
friend constexpr bool is_flag_type(GoodThumbnailFlag) { return true; };
|
||||
|
||||
static constexpr Flags kStreamingSupportedMask = Flags()
|
||||
| Flag::StreamingMaybeYes
|
||||
| Flag::StreamingMaybeNo;
|
||||
@@ -272,9 +291,8 @@ private:
|
||||
|
||||
friend class Serialize::Document;
|
||||
|
||||
LocationType locationType() const;
|
||||
[[nodiscard]] LocationType locationType() const;
|
||||
void validateLottieSticker();
|
||||
void validateGoodThumbnail();
|
||||
void setMaybeSupportsStreaming(bool supports);
|
||||
void setLoadedInMediaCacheLocation();
|
||||
|
||||
@@ -294,8 +312,9 @@ private:
|
||||
|
||||
ImagePtr _thumbnailInline;
|
||||
ImagePtr _thumbnail;
|
||||
std::unique_ptr<Image> _goodThumbnail;
|
||||
Data::ReplyPreview _replyPreview;
|
||||
std::weak_ptr<Data::DocumentMedia> _media;
|
||||
PhotoData *_goodThumbnailPhoto = nullptr;
|
||||
|
||||
not_null<Data::Session*> _owner;
|
||||
|
||||
@@ -304,6 +323,7 @@ private:
|
||||
std::unique_ptr<DocumentAdditionalData> _additional;
|
||||
int32 _duration = -1;
|
||||
mutable Flags _flags = kStreamingSupportedUnknown;
|
||||
GoodThumbnailState _goodThumbnailState = GoodThumbnailState();
|
||||
mutable std::unique_ptr<FileLoader> _loader;
|
||||
|
||||
};
|
||||
|
202
Telegram/SourceFiles/data/data_document_media.cpp
Normal file
202
Telegram/SourceFiles/data/data_document_media.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "data/data_document_media.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "data/data_document_good_thumbnail.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_cloud_themes.h"
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
#include "main/main_session.h"
|
||||
#include "lottie/lottie_animation.h"
|
||||
#include "window/themes/window_theme_preview.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "app.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtGui/QImageReader>
|
||||
|
||||
namespace Data {
|
||||
namespace {
|
||||
|
||||
constexpr auto kReadAreaLimit = 12'032 * 9'024;
|
||||
constexpr auto kWallPaperThumbnailLimit = 960;
|
||||
constexpr auto kMaxVideoFrameArea = 7'680 * 4'320;
|
||||
constexpr auto kGoodThumbQuality = 87;
|
||||
|
||||
enum class FileType {
|
||||
Video,
|
||||
AnimatedSticker,
|
||||
WallPaper,
|
||||
Theme,
|
||||
};
|
||||
|
||||
[[nodiscard]] bool MayHaveGoodThumbnail(not_null<DocumentData*> owner) {
|
||||
return owner->isVideoFile()
|
||||
|| owner->isAnimation()
|
||||
|| owner->isWallPaper()
|
||||
|| owner->isTheme()
|
||||
|| (owner->sticker() && owner->sticker()->animated);
|
||||
}
|
||||
|
||||
[[nodiscard]] QImage PrepareGoodThumbnail(
|
||||
const QString &path,
|
||||
QByteArray data,
|
||||
FileType type) {
|
||||
if (type == FileType::Video) {
|
||||
return ::Media::Clip::PrepareForSending(path, data).thumbnail;
|
||||
} else if (type == FileType::AnimatedSticker) {
|
||||
return Lottie::ReadThumbnail(Lottie::ReadContent(data, path));
|
||||
} else if (type == FileType::Theme) {
|
||||
return Window::Theme::GeneratePreview(data, path);
|
||||
}
|
||||
auto buffer = QBuffer(&data);
|
||||
auto file = QFile(path);
|
||||
auto device = data.isEmpty() ? static_cast<QIODevice*>(&file) : &buffer;
|
||||
auto reader = QImageReader(device);
|
||||
const auto size = reader.size();
|
||||
if (!reader.canRead()
|
||||
|| (size.width() * size.height() > kReadAreaLimit)) {
|
||||
return QImage();
|
||||
}
|
||||
auto result = reader.read();
|
||||
if (!result.width() || !result.height()) {
|
||||
return QImage();
|
||||
}
|
||||
return (result.width() > kWallPaperThumbnailLimit
|
||||
|| result.height() > kWallPaperThumbnailLimit)
|
||||
? result.scaled(
|
||||
kWallPaperThumbnailLimit,
|
||||
kWallPaperThumbnailLimit,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation)
|
||||
: result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DocumentMedia::DocumentMedia(not_null<DocumentData*> owner)
|
||||
: _owner(owner) {
|
||||
}
|
||||
|
||||
DocumentMedia::~DocumentMedia() = default;
|
||||
|
||||
void DocumentMedia::goodThumbnailWanted() {
|
||||
_flags |= Flag::GoodThumbnailWanted;
|
||||
}
|
||||
|
||||
Image *DocumentMedia::goodThumbnail() const {
|
||||
Expects((_flags & Flag::GoodThumbnailWanted) != 0);
|
||||
|
||||
if (!_goodThumbnail) {
|
||||
ReadOrGenerateThumbnail(_owner);
|
||||
}
|
||||
return _goodThumbnail.get();
|
||||
}
|
||||
|
||||
void DocumentMedia::setGoodThumbnail(QImage thumbnail) {
|
||||
if (!(_flags & Flag::GoodThumbnailWanted)) {
|
||||
return;
|
||||
}
|
||||
_goodThumbnail = std::make_unique<Image>(
|
||||
std::make_unique<Images::ImageSource>(std::move(thumbnail), "PNG"));
|
||||
_owner->session().downloaderTaskFinished().notify();
|
||||
}
|
||||
|
||||
void DocumentMedia::GenerateGoodThumbnail(not_null<DocumentData*> document) {
|
||||
const auto data = document->data();
|
||||
const auto type = document->isWallPaper()
|
||||
? FileType::WallPaper
|
||||
: document->isTheme()
|
||||
? FileType::Theme
|
||||
: document->sticker()
|
||||
? FileType::AnimatedSticker
|
||||
: FileType::Video;
|
||||
auto location = document->location().isEmpty()
|
||||
? nullptr
|
||||
: std::make_unique<FileLocation>(document->location());
|
||||
if (data.isEmpty() && !location) {
|
||||
document->setGoodThumbnailChecked(false);
|
||||
return;
|
||||
}
|
||||
const auto guard = base::make_weak(&document->owner().session());
|
||||
crl::async([=, location = std::move(location)] {
|
||||
const auto filepath = (location && location->accessEnable())
|
||||
? location->name()
|
||||
: QString();
|
||||
auto result = PrepareGoodThumbnail(filepath, data, type);
|
||||
auto bytes = QByteArray();
|
||||
if (!result.isNull()) {
|
||||
auto buffer = QBuffer(&bytes);
|
||||
const auto format = (type == FileType::AnimatedSticker)
|
||||
? "WEBP"
|
||||
: (type == FileType::WallPaper && result.hasAlphaChannel())
|
||||
? "PNG"
|
||||
: "JPG";
|
||||
result.save(&buffer, format, kGoodThumbQuality);
|
||||
}
|
||||
if (!filepath.isEmpty()) {
|
||||
location->accessDisable();
|
||||
}
|
||||
const auto cache = bytes.isEmpty() ? QByteArray("(failed)") : bytes;
|
||||
crl::on_main(guard, [=] {
|
||||
document->setGoodThumbnailChecked(true);
|
||||
if (const auto active = document->activeMediaView()) {
|
||||
active->setGoodThumbnail(result);
|
||||
}
|
||||
document->owner().cache().put(
|
||||
document->goodThumbnailCacheKey(),
|
||||
Storage::Cache::Database::TaggedValue{
|
||||
base::duplicate(cache),
|
||||
kImageCacheTag });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void DocumentMedia::CheckGoodThumbnail(not_null<DocumentData*> document) {
|
||||
if (!document->goodThumbnailChecked()) {
|
||||
ReadOrGenerateThumbnail(document);
|
||||
}
|
||||
}
|
||||
|
||||
void DocumentMedia::ReadOrGenerateThumbnail(
|
||||
not_null<DocumentData*> document) {
|
||||
if (document->goodThumbnailGenerating()
|
||||
|| document->goodThumbnailNoData()
|
||||
|| !MayHaveGoodThumbnail(document)) {
|
||||
return;
|
||||
}
|
||||
document->setGoodThumbnailGenerating();
|
||||
|
||||
const auto guard = base::make_weak(&document->session());
|
||||
const auto active = document->activeMediaView();
|
||||
const auto got = [=](QByteArray value) {
|
||||
if (value.isEmpty()) {
|
||||
crl::on_main(guard, [=] {
|
||||
GenerateGoodThumbnail(document);
|
||||
});
|
||||
} else if (active) {
|
||||
crl::async([=] {
|
||||
const auto image = App::readImage(value, nullptr, false);
|
||||
crl::on_main(guard, [=] {
|
||||
document->setGoodThumbnailChecked(true);
|
||||
if (const auto active = document->activeMediaView()) {
|
||||
active->setGoodThumbnail(image);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
crl::on_main(guard, [=] {
|
||||
document->setGoodThumbnailChecked(true);
|
||||
});
|
||||
}
|
||||
};
|
||||
document->owner().cache().get(document->goodThumbnailCacheKey(), got);
|
||||
}
|
||||
|
||||
} // namespace Data
|
43
Telegram/SourceFiles/data/data_document_media.h
Normal file
43
Telegram/SourceFiles/data/data_document_media.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/flags.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
class DocumentMedia final {
|
||||
public:
|
||||
explicit DocumentMedia(not_null<DocumentData*> owner);
|
||||
~DocumentMedia();
|
||||
|
||||
void goodThumbnailWanted();
|
||||
[[nodiscard]] Image *goodThumbnail() const;
|
||||
void setGoodThumbnail(QImage thumbnail);
|
||||
|
||||
// For DocumentData.
|
||||
void validateGoodThumbnail();
|
||||
static void CheckGoodThumbnail(not_null<DocumentData*> document);
|
||||
|
||||
private:
|
||||
enum class Flag : uchar {
|
||||
GoodThumbnailWanted = 0x01,
|
||||
};
|
||||
inline constexpr bool is_flag_type(Flag) { return true; };
|
||||
using Flags = base::flags<Flag>;
|
||||
|
||||
static void ReadOrGenerateThumbnail(not_null<DocumentData*> document);
|
||||
static void GenerateGoodThumbnail(not_null<DocumentData*> document);
|
||||
|
||||
const not_null<DocumentData*> _owner;
|
||||
std::unique_ptr<Image> _goodThumbnail;
|
||||
Flags _flags;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Data
|
@@ -750,23 +750,6 @@ bool MediaFile::updateSentMedia(const MTPMessageMedia &media) {
|
||||
return false;
|
||||
}
|
||||
parent()->history()->owner().documentConvert(_document, *content);
|
||||
|
||||
if (const auto good = _document->goodThumbnail()) {
|
||||
auto bytes = good->bytesForCache();
|
||||
if (const auto length = bytes.size()) {
|
||||
if (length > Storage::kMaxFileInMemory) {
|
||||
LOG(("App Error: Bad thumbnail data for saving to cache."));
|
||||
} else {
|
||||
parent()->history()->owner().cache().putIfEmpty(
|
||||
_document->goodThumbnailCacheKey(),
|
||||
Storage::Cache::Database::TaggedValue(
|
||||
std::move(bytes),
|
||||
Data::kImageCacheTag));
|
||||
_document->refreshGoodThumbnail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -2421,6 +2421,7 @@ void Session::documentConvert(
|
||||
}();
|
||||
const auto oldKey = original->mediaKey();
|
||||
const auto oldCacheKey = original->cacheKey();
|
||||
const auto oldGoodKey = original->goodThumbnailCacheKey();
|
||||
const auto idChanged = (original->id != id);
|
||||
const auto sentSticker = idChanged && (original->sticker() != nullptr);
|
||||
if (idChanged) {
|
||||
@@ -2444,6 +2445,7 @@ void Session::documentConvert(
|
||||
documentApplyFields(original, data);
|
||||
if (idChanged) {
|
||||
cache().moveIfEmpty(oldCacheKey, original->cacheKey());
|
||||
cache().moveIfEmpty(oldGoodKey, original->goodThumbnailCacheKey());
|
||||
if (savedGifs().indexOf(original) >= 0) {
|
||||
Local::writeSavedGifs();
|
||||
}
|
||||
@@ -3180,10 +3182,10 @@ void Session::unregisterPlayingVideoFile(not_null<ViewElement*> view) {
|
||||
if (i != _playingVideoFiles.end()) {
|
||||
if (!--i->second) {
|
||||
_playingVideoFiles.erase(i);
|
||||
unregisterHeavyViewPart(view);
|
||||
view->checkHeavyPart();
|
||||
}
|
||||
} else {
|
||||
unregisterHeavyViewPart(view);
|
||||
view->checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3205,7 +3207,7 @@ void Session::checkPlayingVideoFiles() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
unregisterHeavyViewPart(view);
|
||||
view->checkHeavyPart();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -237,15 +237,7 @@ bool WebPageData::applyChanges(
|
||||
}
|
||||
|
||||
void WebPageData::replaceDocumentGoodThumbnail() {
|
||||
if (!document || !photo || !document->goodThumbnail()) {
|
||||
return;
|
||||
if (document && photo) {
|
||||
document->setGoodThumbnailPhoto(photo);
|
||||
}
|
||||
const auto &location = photo->large()->location();
|
||||
if (location.valid()) {
|
||||
document->replaceGoodThumbnail(
|
||||
std::make_unique<Images::StorageSource>(
|
||||
location,
|
||||
photo->large()->bytesSize()));
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user