2
0
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:
John Preston
2020-03-27 15:40:50 +04:00
parent 61647275e8
commit 7db53599e8
29 changed files with 545 additions and 168 deletions

View File

@@ -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) {

View File

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

View 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

View 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

View File

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

View File

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

View File

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