Request song covers through MTProto.

This commit is contained in:
John Preston 2022-08-01 15:14:37 +03:00
parent be133fce78
commit 087074fea4
16 changed files with 145 additions and 185 deletions

View File

@ -1177,8 +1177,6 @@ PRIVATE
storage/storage_account.h
storage/storage_cloud_blob.cpp
storage/storage_cloud_blob.h
storage/storage_cloud_song_cover.cpp
storage/storage_cloud_song_cover.h
storage/storage_domain.cpp
storage/storage_domain.h
storage/storage_facade.cpp

View File

@ -850,6 +850,7 @@ inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector
inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation;
inputWebFileAudioAlbumThumbLocation#f46fe924 flags:# document:flags.0?InputDocument title:flags.1?string performer:flags.1?string = InputWebFileLocation;
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;

View File

@ -37,7 +37,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_gif.h"
#include "window/window_session_controller.h"
#include "storage/cache/storage_cache_database.h"
#include "storage/storage_cloud_song_cover.h"
#include "ui/boxes/confirm_box.h"
#include "ui/image/image.h"
#include "ui/text/text_utilities.h"
@ -53,6 +52,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kDefaultCoverThumbnailSize = 100;
const auto kLottieStickerDimensions = QSize(
kStickerSideSize,
kStickerSideSize);
@ -407,13 +408,7 @@ void DocumentData::setattributes(
songData->duration = data.vduration().v;
songData->title = qs(data.vtitle().value_or_empty());
songData->performer = qs(data.vperformer().value_or_empty());
if (!hasThumbnail()
&& !songData->title.isEmpty()
&& !songData->performer.isEmpty()) {
Storage::CloudSongCover::LoadThumbnailFromExternal(this);
}
refreshPossibleCoverThumbnail();
}
}, [&](const MTPDdocumentAttributeFilename &data) {
setFileName(qs(data.vfile_name()));
@ -556,7 +551,9 @@ bool DocumentData::isPremiumEmoji() const {
}
bool DocumentData::hasThumbnail() const {
return _thumbnail.location.valid();
return _thumbnail.location.valid()
&& !thumbnailFailed()
&& !(_flags & Flag::PossibleCoverThumbnail);
}
bool DocumentData::thumbnailLoading() const {
@ -576,6 +573,7 @@ void DocumentData::loadThumbnail(Data::FileOrigin origin) {
return true;
};
const auto done = [=](QImage result, QByteArray) {
_flags &= ~Flag::PossibleCoverThumbnail;
if (const auto active = activeMediaView()) {
active->setThumbnail(std::move(result));
}
@ -1143,6 +1141,31 @@ bool DocumentData::saveFromDataChecked() {
return true;
}
void DocumentData::refreshPossibleCoverThumbnail() {
Expects(isSong());
if (_thumbnail.location.valid()) {
return;
}
const auto songData = song();
if (songData->performer.isEmpty()
|| songData->title.isEmpty()
// Ignore cover for voice chat records.
|| hasMimeType(qstr("audio/ogg"))) {
return;
}
const auto size = kDefaultCoverThumbnailSize;
const auto location = ImageWithLocation{
.location = ImageLocation(
{ AudioAlbumThumbLocation{ id } },
size,
size)
};
_flags |= Flag::PossibleCoverThumbnail;
updateThumbnails({}, location, {}, false);
loadThumbnail({});
}
bool DocumentData::isStickerSetInstalled() const {
Expects(sticker() != nullptr);

View File

@ -285,6 +285,7 @@ private:
InlineThumbnailIsPath = 0x080,
ForceToCache = 0x100,
PremiumSticker = 0x200,
PossibleCoverThumbnail = 0x400,
};
using Flags = base::flags<Flag>;
friend constexpr bool is_flag_type(Flag) { return true; };
@ -326,6 +327,8 @@ private:
bool saveFromDataChecked();
void refreshPossibleCoverThumbnail();
const not_null<Data::Session*> _owner;
// Two types of location: from MTProto by dc+access or from web by url

View File

@ -1085,9 +1085,7 @@ rpl::producer<Ui::DownloadBarContent> MakeDownloadBarContent() {
auto &manager = Core::App().downloadManager();
const auto resolveThumbnailRecursive = [=](auto &&self) -> bool {
if (state->document
&& (!state->document->hasThumbnail()
|| state->document->thumbnailFailed())) {
if (state->document && !state->document->hasThumbnail()) {
state->media = nullptr;
}
if (!state->media) {

View File

@ -18,6 +18,7 @@ constexpr auto kDocumentCacheTag = 0x0000000000000100ULL;
constexpr auto kDocumentCacheMask = 0x00000000000000FFULL;
constexpr auto kDocumentThumbCacheTag = 0x0000000000000200ULL;
constexpr auto kDocumentThumbCacheMask = 0x00000000000000FFULL;
constexpr auto kAudioAlbumThumbCacheTag = 0x0000000000000300ULL;
constexpr auto kWebDocumentCacheTag = 0x0000020000000000ULL;
constexpr auto kUrlCacheTag = 0x0000030000000000ULL;
constexpr auto kGeoPointCacheTag = 0x0000040000000000ULL;
@ -86,6 +87,14 @@ Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location) {
};
}
Storage::Cache::Key AudioAlbumThumbCacheKey(
const AudioAlbumThumbLocation &location) {
return Storage::Cache::Key{
Data::kAudioAlbumThumbCacheTag,
location.documentId,
};
}
} // namespace Data
void MessageCursor::fillFrom(not_null<const Ui::InputField*> field) {

View File

@ -49,6 +49,8 @@ Storage::Cache::Key DocumentThumbCacheKey(int32 dcId, uint64 id);
Storage::Cache::Key WebDocumentCacheKey(const WebFileLocation &location);
Storage::Cache::Key UrlCacheKey(const QString &location);
Storage::Cache::Key GeoPointCacheKey(const GeoPointLocation &location);
Storage::Cache::Key AudioAlbumThumbCacheKey(
const AudioAlbumThumbLocation &location);
constexpr auto kImageCacheTag = uint8(0x01);
constexpr auto kStickerCacheTag = uint8(0x02);

View File

@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_auth_key.h"
#include "mtproto/mtproto_response.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "apiwrap.h"
#include "base/openssl_help.h"
@ -557,6 +559,22 @@ mtpRequestId DownloadMtprotoTask::sendRequest(
}).fail([=](const MTP::Error &error, mtpRequestId id) {
partFailed(error, id);
}).toDC(shiftedDcId).send();
}, [&](const AudioAlbumThumbLocation &location) {
using Flag = MTPDinputWebFileAudioAlbumThumbLocation::Flag;
const auto owner = &api().session().data();
return api().request(MTPupload_GetWebFile(
MTP_inputWebFileAudioAlbumThumbLocation(
MTP_flags(Flag::f_document),
owner->document(location.documentId)->mtpInput(),
MTPstring(),
MTPstring()),
MTP_int(offset),
MTP_int(limit)
)).done([=](const MTPupload_WebFile &result, mtpRequestId id) {
webPartLoaded(result, id);
}).fail([=](const MTP::Error &error, mtpRequestId id) {
partFailed(error, id);
}).toDC(shiftedDcId).send();
}, [&](const StorageFileLocation &location) {
const auto reference = location.fileReference();
return api().request(MTPupload_GetFile(

View File

@ -127,7 +127,8 @@ public:
std::variant<
StorageFileLocation,
WebFileLocation,
GeoPointLocation> data;
GeoPointLocation,
AudioAlbumThumbLocation> data;
};
DownloadMtprotoTask(

View File

@ -525,6 +525,15 @@ std::unique_ptr<FileLoader> CreateFileLoader(
fromCloud,
autoLoading,
cacheTag);
}, [&](const AudioAlbumThumbLocation &data) {
result = std::make_unique<mtpFileLoader>(
session,
data,
loadSize,
fullSize,
fromCloud,
autoLoading,
cacheTag);
}, [&](const InMemoryLocation &data) {
result = std::make_unique<FromMemoryLoader>(
session,

View File

@ -89,6 +89,30 @@ mtpFileLoader::mtpFileLoader(
{ location }) {
}
mtpFileLoader::mtpFileLoader(
not_null<Main::Session*> session,
const AudioAlbumThumbLocation &location,
int64 loadSize,
int64 fullSize,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag)
: FileLoader(
session,
QString(),
loadSize,
fullSize,
UnknownFileLocation,
LoadToCacheAsWell,
fromCloud,
autoLoading,
cacheTag)
, DownloadMtprotoTask(
&session->downloader(),
session->serverConfig().webFileDcId,
{ location }) {
}
mtpFileLoader::~mtpFileLoader() {
if (!_finished) {
cancel();
@ -184,6 +208,8 @@ Storage::Cache::Key mtpFileLoader::cacheKey() const {
return Data::GeoPointCacheKey(location);
}, [&](const StorageFileLocation &location) {
return location.cacheKey();
}, [&](const AudioAlbumThumbLocation &location) {
return Data::AudioAlbumThumbCacheKey(location);
});
}

View File

@ -42,6 +42,14 @@ public:
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag);
mtpFileLoader(
not_null<Main::Session*> session,
const AudioAlbumThumbLocation &location,
int64 loadSize,
int64 fullSize,
LoadFromCloudSetting fromCloud,
bool autoLoading,
uint8 cacheTag);
~mtpFileLoader();
Data::FileOrigin fileOrigin() const override;

View File

@ -1,154 +0,0 @@
/*
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 "storage/storage_cloud_song_cover.h"
#include "data/data_cloud_file.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "main/main_session.h"
#include "storage/file_download.h"
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
namespace Storage::CloudSongCover {
namespace {
constexpr auto kMaxResponseSize = 1024 * 1024;
constexpr auto kDefaultCoverSize = 100;
struct Responce {
const QString artworkUrl;
const int size;
};
auto Location(const QString &url) {
return DownloadLocation{ PlainUrlLocation{ url } };
}
auto JsonUrl(not_null<SongData*> song) {
return QString("https://itunes.apple.com/search?term=" \
"%1 %2&entity=song&limit=4").arg(song->performer, song->title);
}
// Dummy JSON responce.
// {
// "resultCount": 2,
// "results": [
// {
// "artworkUrl100": ""
// },
// {
// "artworkUrl100": ""
// }
// ]
// }
std::optional<Responce> ParseResponce(const QByteArray &response) {
if (response.size() >= kMaxResponseSize) {
return std::nullopt;
}
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
const auto document = QJsonDocument::fromJson(response, &error);
const auto log = [](const QString &message) {
DEBUG_LOG(("Parse Artwork JSON Error: %1.").arg(message));
};
if (error.error != QJsonParseError::NoError) {
log(error.errorString());
return std::nullopt;
} else if (!document.isObject()) {
log("not an object received in JSON");
return std::nullopt;
}
const auto results = document.object().value("results");
if (!results.isArray()) {
log("'results' field not found");
return std::nullopt;
}
const auto resultsArray = results.toArray();
if (resultsArray.empty()) {
return std::nullopt;
}
const auto artworkUrl = resultsArray.first().toObject()
.value("artworkUrl100").toString();
if (artworkUrl.isEmpty()) {
log("'artworkUrl100' field is empty");
return std::nullopt;
}
return Responce{ artworkUrl, kDefaultCoverSize };
}
void LoadAndApplyThumbnail(
not_null<DocumentData*> document,
const Responce &responce) {
const auto size = responce.size;
const auto imageWithLocation = ImageWithLocation{
.location = ImageLocation(Location(responce.artworkUrl), size, size)
};
document->updateThumbnails(
InlineImageLocation(),
imageWithLocation,
ImageWithLocation{ .location = ImageLocation() },
document->isPremiumSticker());
document->loadThumbnail(Data::FileOrigin());
}
} // namespace
void LoadThumbnailFromExternal(not_null<DocumentData*> document) {
const auto songData = document->song();
if (!songData
|| songData->performer.isEmpty()
|| songData->title.isEmpty()
// Ignore cover for voice chat records.
|| document->hasMimeType(qstr("audio/ogg"))) {
return;
}
const auto &size = kDefaultCoverSize;
const auto jsonLocation = ImageWithLocation{
.location = ImageLocation(Location(JsonUrl(songData)), size, size)
};
const auto jsonCloudFile = std::make_shared<Data::CloudFile>();
Data::UpdateCloudFile(
*jsonCloudFile,
jsonLocation,
document->owner().cache(),
0, // Cache tag.
nullptr,
nullptr);
auto done = [=](const QByteArray &result) {
if (!jsonCloudFile) {
return;
}
if (const auto responce = ParseResponce(result)) {
LoadAndApplyThumbnail(document, *responce);
}
};
Data::LoadCloudFile(
&document->session(),
*jsonCloudFile,
Data::FileOrigin(),
LoadFromCloudOrLocal,
true,
0,
[] { return true; },
std::move(done));
}
} // namespace Storage::CloudSongCover

View File

@ -1,16 +0,0 @@
/*
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
class DocumentData;
namespace Storage::CloudSongCover {
void LoadThumbnailFromExternal(not_null<DocumentData*> document);
} // namespace Storage::CloudSongCover

View File

@ -34,6 +34,7 @@ enum class NonStorageLocationType : quint8 {
Geo,
Url,
Memory,
AudioAlbumThumb,
};
MTPInputPeer GenerateInputPeer(
@ -710,6 +711,11 @@ InMemoryKey inMemoryKey(const PlainUrlLocation &location) {
return result;
}
InMemoryKey inMemoryKey(const AudioAlbumThumbLocation &location) {
const auto key = Data::AudioAlbumThumbCacheKey(location);
return { key.high, key.low };
}
InMemoryKey inMemoryKey(const InMemoryLocation &location) {
auto result = InMemoryKey();
const auto &data = location.bytes;
@ -808,6 +814,10 @@ QByteArray DownloadLocation::serialize() const {
<< qint32(data.height)
<< qint32(data.zoom)
<< qint32(data.scale);
}, [&](const AudioAlbumThumbLocation &data) {
stream
<< quint8(NonStorageLocationType::AudioAlbumThumb)
<< quint64(data.documentId);
}, [&](const PlainUrlLocation &data) {
stream << quint8(NonStorageLocationType::Url) << data.url.toUtf8();
}, [&](const InMemoryLocation &data) {
@ -830,6 +840,8 @@ int DownloadLocation::serializeSize() const {
result += 2 * sizeof(qreal) + sizeof(quint64) + 4 * sizeof(qint32);
}, [&](const PlainUrlLocation &data) {
result += Serialize::bytearraySize(data.url.toUtf8());
}, [&](const AudioAlbumThumbLocation &data) {
result += sizeof(quint64);
}, [&](const InMemoryLocation &data) {
result += Serialize::bytearraySize(data.bytes);
});
@ -884,6 +896,15 @@ std::optional<DownloadLocation> DownloadLocation::FromSerialized(
: std::nullopt;
} break;
case NonStorageLocationType::AudioAlbumThumb: {
quint64 id = 0;
stream >> id;
return (stream.status() == QDataStream::Ok)
? std::make_optional(DownloadLocation{
AudioAlbumThumbLocation{ id } })
: std::nullopt;
} break;
case NonStorageLocationType::Url: {
QByteArray utf;
stream >> utf;
@ -933,6 +954,8 @@ Storage::Cache::Key DownloadLocation::cacheKey() const {
return data.url.isEmpty()
? Storage::Cache::Key()
: Data::UrlCacheKey(data.url);
}, [](const AudioAlbumThumbLocation &data) {
return Data::AudioAlbumThumbCacheKey(data);
}, [](const InMemoryLocation &data) {
return Storage::Cache::Key();
});
@ -953,6 +976,8 @@ bool DownloadLocation::valid() const {
return !data.isNull();
}, [](const PlainUrlLocation &data) {
return !data.url.isEmpty();
}, [](const AudioAlbumThumbLocation &data) {
return data.documentId != 0;
}, [](const InMemoryLocation &data) {
return !data.bytes.isEmpty();
});

View File

@ -408,6 +408,14 @@ inline bool operator>=(
return !(a < b);
}
struct AudioAlbumThumbLocation {
uint64 documentId = 0;
friend inline auto operator<=>(
AudioAlbumThumbLocation,
AudioAlbumThumbLocation) = default;
};
struct InMemoryLocation {
QByteArray bytes;
@ -454,6 +462,7 @@ public:
WebFileLocation,
GeoPointLocation,
PlainUrlLocation,
AudioAlbumThumbLocation,
InMemoryLocation> data;
[[nodiscard]] QByteArray serialize() const;