tdesktop/Telegram/SourceFiles/storage/storage_cloud_song_cover.cpp

152 lines
3.7 KiB
C++

/*
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).arg(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(
QByteArray(),
imageWithLocation,
ImageWithLocation{ .location = ImageLocation() });
document->loadThumbnail(Data::FileOrigin());
}
}
void LoadThumbnailFromExternal(not_null<DocumentData*> document) {
const auto songData = document->song();
if (!songData
|| songData->performer.isEmpty()
|| songData->title.isEmpty()) {
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