diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 6f9bafd8c2..0a1aed46c7 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 diff --git a/Telegram/Resources/tl/api.tl b/Telegram/Resources/tl/api.tl index 88b6bcad27..f74b7b1196 100644 --- a/Telegram/Resources/tl/api.tl +++ b/Telegram/Resources/tl/api.tl @@ -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; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index 376918eaa0..7386ce3714 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -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); diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 3af5f76162..0b11ed013f 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -285,6 +285,7 @@ private: InlineThumbnailIsPath = 0x080, ForceToCache = 0x100, PremiumSticker = 0x200, + PossibleCoverThumbnail = 0x400, }; using Flags = base::flags; friend constexpr bool is_flag_type(Flag) { return true; }; @@ -326,6 +327,8 @@ private: bool saveFromDataChecked(); + void refreshPossibleCoverThumbnail(); + const not_null _owner; // Two types of location: from MTProto by dc+access or from web by url diff --git a/Telegram/SourceFiles/data/data_download_manager.cpp b/Telegram/SourceFiles/data/data_download_manager.cpp index c1cdf47bab..2d08b0a8a5 100644 --- a/Telegram/SourceFiles/data/data_download_manager.cpp +++ b/Telegram/SourceFiles/data/data_download_manager.cpp @@ -1085,9 +1085,7 @@ rpl::producer 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) { diff --git a/Telegram/SourceFiles/data/data_types.cpp b/Telegram/SourceFiles/data/data_types.cpp index 7060ed3344..0873bd7ec3 100644 --- a/Telegram/SourceFiles/data/data_types.cpp +++ b/Telegram/SourceFiles/data/data_types.cpp @@ -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 field) { diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h index 5770156713..3d00ec152b 100644 --- a/Telegram/SourceFiles/data/data_types.h +++ b/Telegram/SourceFiles/data/data_types.h @@ -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); diff --git a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp index cc154b0b6f..9b46826f5b 100644 --- a/Telegram/SourceFiles/storage/download_manager_mtproto.cpp +++ b/Telegram/SourceFiles/storage/download_manager_mtproto.cpp @@ -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( diff --git a/Telegram/SourceFiles/storage/download_manager_mtproto.h b/Telegram/SourceFiles/storage/download_manager_mtproto.h index e402f536da..6cc7421f66 100644 --- a/Telegram/SourceFiles/storage/download_manager_mtproto.h +++ b/Telegram/SourceFiles/storage/download_manager_mtproto.h @@ -127,7 +127,8 @@ public: std::variant< StorageFileLocation, WebFileLocation, - GeoPointLocation> data; + GeoPointLocation, + AudioAlbumThumbLocation> data; }; DownloadMtprotoTask( diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index 3960d95dd2..6e56f549c8 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -525,6 +525,15 @@ std::unique_ptr CreateFileLoader( fromCloud, autoLoading, cacheTag); + }, [&](const AudioAlbumThumbLocation &data) { + result = std::make_unique( + session, + data, + loadSize, + fullSize, + fromCloud, + autoLoading, + cacheTag); }, [&](const InMemoryLocation &data) { result = std::make_unique( session, diff --git a/Telegram/SourceFiles/storage/file_download_mtproto.cpp b/Telegram/SourceFiles/storage/file_download_mtproto.cpp index da058e6001..5e96a54fe0 100644 --- a/Telegram/SourceFiles/storage/file_download_mtproto.cpp +++ b/Telegram/SourceFiles/storage/file_download_mtproto.cpp @@ -89,6 +89,30 @@ mtpFileLoader::mtpFileLoader( { location }) { } +mtpFileLoader::mtpFileLoader( + not_null 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); }); } diff --git a/Telegram/SourceFiles/storage/file_download_mtproto.h b/Telegram/SourceFiles/storage/file_download_mtproto.h index 71ba3a243b..a840bfc824 100644 --- a/Telegram/SourceFiles/storage/file_download_mtproto.h +++ b/Telegram/SourceFiles/storage/file_download_mtproto.h @@ -42,6 +42,14 @@ public: LoadFromCloudSetting fromCloud, bool autoLoading, uint8 cacheTag); + mtpFileLoader( + not_null session, + const AudioAlbumThumbLocation &location, + int64 loadSize, + int64 fullSize, + LoadFromCloudSetting fromCloud, + bool autoLoading, + uint8 cacheTag); ~mtpFileLoader(); Data::FileOrigin fileOrigin() const override; diff --git a/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp b/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp deleted file mode 100644 index cb2a6a2031..0000000000 --- a/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp +++ /dev/null @@ -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 -#include -#include -#include - -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 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 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 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 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::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 diff --git a/Telegram/SourceFiles/storage/storage_cloud_song_cover.h b/Telegram/SourceFiles/storage/storage_cloud_song_cover.h deleted file mode 100644 index 8b649f9eaa..0000000000 --- a/Telegram/SourceFiles/storage/storage_cloud_song_cover.h +++ /dev/null @@ -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 document); - -} // namespace Storage::CloudSongCover diff --git a/Telegram/SourceFiles/ui/image/image_location.cpp b/Telegram/SourceFiles/ui/image/image_location.cpp index dd9fa1658f..d1fdde5b64 100644 --- a/Telegram/SourceFiles/ui/image/image_location.cpp +++ b/Telegram/SourceFiles/ui/image/image_location.cpp @@ -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::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(); }); diff --git a/Telegram/SourceFiles/ui/image/image_location.h b/Telegram/SourceFiles/ui/image/image_location.h index 21f8a69236..98bb7024ea 100644 --- a/Telegram/SourceFiles/ui/image/image_location.h +++ b/Telegram/SourceFiles/ui/image/image_location.h @@ -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;