tdlight/td/telegram/Photo.cpp
Andrea Cavalli 45e855f89d Remove most memory related features
I can't maintain anymore this amount of features while keeping the library constantly updated and without bugs. Every merge was taking me multiple hours of revisioning the code. I give up.
From this commit onwards TDLight will only have small useful customizations that are easy to maintain.
Now the people relying on the OptimizeMemory method can restart the session every N hours to free up the memory.
The real way to keep a low memory usage must involve a huge refactoring to allow the unloading of the caches into the sqlite database, similar to what's already happening with messages data. Only Levlam has the ability to implement this without needing to merge the upstream everytime.
2021-09-25 22:11:42 +02:00

1008 lines
39 KiB
C++

//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/Photo.h"
#include "td/telegram/files/FileEncryptionKey.h"
#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/net/DcId.h"
#include "td/telegram/ConfigShared.h"
#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Random.h"
#include "td/utils/SliceBuilder.h"
#include <algorithm>
#include <cmath>
#include <limits>
namespace td {
static uint16 get_dimension(int32 size, const char *source) {
if (size < 0 || size > 65535) {
LOG(ERROR) << "Wrong image dimension = " << size << " from " << source;
return 0;
}
return narrow_cast<uint16>(size);
}
Dimensions get_dimensions(int32 width, int32 height, const char *source) {
Dimensions result;
result.width = get_dimension(width, source);
result.height = get_dimension(height, source);
if (result.width == 0 || result.height == 0) {
result.width = 0;
result.height = 0;
}
return result;
}
static uint32 get_pixel_count(const Dimensions &dimensions) {
return static_cast<uint32>(dimensions.width) * static_cast<uint32>(dimensions.height);
}
bool operator==(const Dimensions &lhs, const Dimensions &rhs) {
return lhs.width == rhs.width && lhs.height == rhs.height;
}
bool operator!=(const Dimensions &lhs, const Dimensions &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions) {
return string_builder << "(" << dimensions.width << ", " << dimensions.height << ")";
}
td_api::object_ptr<td_api::minithumbnail> get_minithumbnail_object(const string &packed) {
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
return nullptr;
}
if (packed.size() < 3) {
return nullptr;
}
if (packed[0] == '\x01') {
static const string header =
base64_decode(
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////"
"m8H///"
"/6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/"
"wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/"
"8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0R"
"FRkd"
"ISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2"
"uHi4"
"+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/"
"8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkN"
"ERUZ"
"HSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2"
"Nna4"
"uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=")
.move_as_ok();
static const string footer = base64_decode("/9k=").move_as_ok();
auto result = td_api::make_object<td_api::minithumbnail>();
result->height_ = static_cast<unsigned char>(packed[1]);
result->width_ = static_cast<unsigned char>(packed[2]);
result->data_ = PSTRING() << header.substr(0, 164) << packed[1] << header[165] << packed[2] << header.substr(167)
<< packed.substr(3) << footer;
return result;
}
return nullptr;
}
static td_api::object_ptr<td_api::ThumbnailFormat> get_thumbnail_format_object(PhotoFormat format) {
switch (format) {
case PhotoFormat::Jpeg:
return td_api::make_object<td_api::thumbnailFormatJpeg>();
case PhotoFormat::Png:
return td_api::make_object<td_api::thumbnailFormatPng>();
case PhotoFormat::Webp:
return td_api::make_object<td_api::thumbnailFormatWebp>();
case PhotoFormat::Gif:
return td_api::make_object<td_api::thumbnailFormatGif>();
case PhotoFormat::Tgs:
return td_api::make_object<td_api::thumbnailFormatTgs>();
case PhotoFormat::Mpeg4:
return td_api::make_object<td_api::thumbnailFormatMpeg4>();
default:
UNREACHABLE();
return nullptr;
}
}
static StringBuilder &operator<<(StringBuilder &string_builder, PhotoFormat format) {
switch (format) {
case PhotoFormat::Jpeg:
return string_builder << "jpg";
case PhotoFormat::Png:
return string_builder << "png";
case PhotoFormat::Webp:
return string_builder << "webp";
case PhotoFormat::Gif:
return string_builder << "gif";
case PhotoFormat::Tgs:
return string_builder << "tgs";
case PhotoFormat::Mpeg4:
return string_builder << "mp4";
default:
UNREACHABLE();
return string_builder;
}
}
static FileId register_photo(FileManager *file_manager, const PhotoSizeSource &source, int64 id, int64 access_hash,
std::string file_reference, DialogId owner_dialog_id, int32 file_size, DcId dc_id,
PhotoFormat format) {
LOG(DEBUG) << "Receive " << format << " photo " << id << " of type " << source.get_file_type() << " from " << dc_id;
auto suggested_name = PSTRING() << source.get_unique_name(id) << '.' << format;
auto file_location_source = owner_dialog_id.get_type() == DialogType::SecretChat ? FileLocationSource::FromUser
: FileLocationSource::FromServer;
return file_manager->register_remote(
FullRemoteFileLocation(source, id, access_hash, dc_id, std::move(file_reference)), file_location_source,
owner_dialog_id, file_size, 0, std::move(suggested_name));
}
ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash,
tl_object_ptr<telegram_api::UserProfilePhoto> &&profile_photo_ptr) {
ProfilePhoto result;
int32 profile_photo_id =
profile_photo_ptr == nullptr ? telegram_api::userProfilePhotoEmpty::ID : profile_photo_ptr->get_id();
switch (profile_photo_id) {
case telegram_api::userProfilePhotoEmpty::ID:
break;
case telegram_api::userProfilePhoto::ID: {
auto profile_photo = move_tl_object_as<telegram_api::userProfilePhoto>(profile_photo_ptr);
auto dc_id = DcId::create(profile_photo->dc_id_);
result.has_animation = (profile_photo->flags_ & telegram_api::userProfilePhoto::HAS_VIDEO_MASK) != 0;
result.id = profile_photo->photo_id_;
if (!G()->shared_config().get_option_boolean("disable_minithumbnails")) {
result.minithumbnail = profile_photo->stripped_thumb_.as_slice().str();
}
result.small_file_id =
register_photo(file_manager, {DialogId(user_id), user_access_hash, false}, result.id, 0 /*access_hash*/,
"" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
result.big_file_id =
register_photo(file_manager, {DialogId(user_id), user_access_hash, true}, result.id, 0 /*access_hash*/,
"" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
break;
}
default:
UNREACHABLE();
break;
}
return result;
}
tl_object_ptr<td_api::profilePhoto> get_profile_photo_object(FileManager *file_manager,
const ProfilePhoto &profile_photo) {
if (!profile_photo.small_file_id.is_valid()) {
return nullptr;
}
return td_api::make_object<td_api::profilePhoto>(
profile_photo.id, file_manager->get_file_object(profile_photo.small_file_id),
file_manager->get_file_object(profile_photo.big_file_id), get_minithumbnail_object(profile_photo.minithumbnail),
profile_photo.has_animation);
}
bool operator==(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
bool location_differs = lhs.small_file_id != rhs.small_file_id || lhs.big_file_id != rhs.big_file_id;
bool id_differs = lhs.id != rhs.id;
if (location_differs) {
return false;
}
return lhs.has_animation == rhs.has_animation && lhs.minithumbnail == rhs.minithumbnail && !id_differs;
}
bool operator!=(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const ProfilePhoto &profile_photo) {
return string_builder << "<ID = " << profile_photo.id << ", small_file_id = " << profile_photo.small_file_id
<< ", big_file_id = " << profile_photo.big_file_id
<< ", has_animation = " << profile_photo.has_animation << ">";
}
DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash,
tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
int32 chat_photo_id = chat_photo_ptr == nullptr ? telegram_api::chatPhotoEmpty::ID : chat_photo_ptr->get_id();
DialogPhoto result;
switch (chat_photo_id) {
case telegram_api::chatPhotoEmpty::ID:
break;
case telegram_api::chatPhoto::ID: {
auto chat_photo = move_tl_object_as<telegram_api::chatPhoto>(chat_photo_ptr);
auto dc_id = DcId::create(chat_photo->dc_id_);
result.has_animation = (chat_photo->flags_ & telegram_api::chatPhoto::HAS_VIDEO_MASK) != 0;
result.minithumbnail = chat_photo->stripped_thumb_.as_slice().str();
result.small_file_id = register_photo(file_manager, {dialog_id, dialog_access_hash, false}, chat_photo->photo_id_,
0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
result.big_file_id = register_photo(file_manager, {dialog_id, dialog_access_hash, true}, chat_photo->photo_id_, 0,
"", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
break;
}
default:
UNREACHABLE();
break;
}
return result;
}
tl_object_ptr<td_api::chatPhotoInfo> get_chat_photo_info_object(FileManager *file_manager,
const DialogPhoto *dialog_photo) {
if (dialog_photo == nullptr || !dialog_photo->small_file_id.is_valid()) {
return nullptr;
}
return td_api::make_object<td_api::chatPhotoInfo>(file_manager->get_file_object(dialog_photo->small_file_id),
file_manager->get_file_object(dialog_photo->big_file_id),
get_minithumbnail_object(dialog_photo->minithumbnail),
dialog_photo->has_animation);
}
vector<FileId> dialog_photo_get_file_ids(const DialogPhoto &dialog_photo) {
vector<FileId> result;
if (dialog_photo.small_file_id.is_valid()) {
result.push_back(dialog_photo.small_file_id);
}
if (dialog_photo.big_file_id.is_valid()) {
result.push_back(dialog_photo.big_file_id);
}
return result;
}
DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id) {
DialogPhoto result;
if (!photo.is_empty()) {
for (auto &size : photo.photos) {
if (size.type == 'a') {
result.small_file_id = size.file_id;
} else if (size.type == 'c') {
result.big_file_id = size.file_id;
}
}
result.minithumbnail = photo.minithumbnail;
result.has_animation = !photo.animations.empty();
if (!result.small_file_id.is_valid() || !result.big_file_id.is_valid()) {
LOG(ERROR) << "Failed to convert " << photo << " to chat photo of " << dialog_id;
return DialogPhoto();
}
}
return result;
}
ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo) {
ProfilePhoto result;
static_cast<DialogPhoto &>(result) = as_fake_dialog_photo(photo, DialogId(user_id));
if (!result.small_file_id.is_valid()) {
return result;
}
auto reregister_photo = [&](bool is_big, FileId file_id) {
auto file_view = file_manager->get_file_view(file_id);
CHECK(file_view.has_remote_location());
auto remote = file_view.remote_location();
CHECK(remote.is_photo());
CHECK(!remote.is_web());
remote.set_source({DialogId(user_id), user_access_hash, is_big});
return file_manager->register_remote(std::move(remote), FileLocationSource::FromServer, DialogId(),
file_view.size(), file_view.expected_size(), file_view.remote_name());
};
result.id = photo.id.get();
result.small_file_id = reregister_photo(false, result.small_file_id);
result.big_file_id = reregister_photo(true, result.big_file_id);
return result;
}
bool operator==(const DialogPhoto &lhs, const DialogPhoto &rhs) {
return lhs.small_file_id == rhs.small_file_id && lhs.big_file_id == rhs.big_file_id &&
lhs.minithumbnail == rhs.minithumbnail && lhs.has_animation == rhs.has_animation;
}
bool operator!=(const DialogPhoto &lhs, const DialogPhoto &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo) {
return string_builder << "<small_file_id = " << dialog_photo.small_file_id
<< ", big_file_id = " << dialog_photo.big_file_id
<< ", has_animation = " << dialog_photo.has_animation << ">";
}
PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id,
int32 width, int32 height) {
if (bytes.empty()) {
return PhotoSize();
}
PhotoSize res;
res.type = 't';
res.dimensions = get_dimensions(width, height, "get_secret_thumbnail_photo_size");
res.size = narrow_cast<int32>(bytes.size());
// generate some random remote location to save
auto dc_id = DcId::invalid();
auto photo_id = -(Random::secure_int64() & std::numeric_limits<int64>::max());
res.file_id = file_manager->register_remote(
FullRemoteFileLocation(PhotoSizeSource(FileType::EncryptedThumbnail, 't'), photo_id, 0, dc_id, string()),
FileLocationSource::FromServer, owner_dialog_id, res.size, 0,
PSTRING() << static_cast<uint64>(photo_id) << ".jpg");
file_manager->set_content(res.file_id, std::move(bytes));
return res;
}
Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id,
int64 access_hash, std::string file_reference, DcId dc_id,
DialogId owner_dialog_id, tl_object_ptr<telegram_api::PhotoSize> &&size_ptr,
PhotoFormat format) {
CHECK(size_ptr != nullptr);
string type;
PhotoSize res;
BufferSlice content;
switch (size_ptr->get_id()) {
case telegram_api::photoSizeEmpty::ID:
return std::move(res);
case telegram_api::photoSize::ID: {
auto size = move_tl_object_as<telegram_api::photoSize>(size_ptr);
type = std::move(size->type_);
res.dimensions = get_dimensions(size->w_, size->h_, "photoSize");
res.size = size->size_;
break;
}
case telegram_api::photoCachedSize::ID: {
auto size = move_tl_object_as<telegram_api::photoCachedSize>(size_ptr);
type = std::move(size->type_);
CHECK(size->bytes_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max()));
res.dimensions = get_dimensions(size->w_, size->h_, "photoCachedSize");
res.size = static_cast<int32>(size->bytes_.size());
content = std::move(size->bytes_);
break;
}
case telegram_api::photoStrippedSize::ID: {
auto size = move_tl_object_as<telegram_api::photoStrippedSize>(size_ptr);
if (format != PhotoFormat::Jpeg) {
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
LOG(DEBUG) << "Receive unexpected JPEG minithumbnail";
} else {
LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo " << id << " from " << source << " of format "
<< format;
}
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
return std::string("");
} else {
return std::move(res);
}
}
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
return std::string("");
} else {
return size->bytes_.as_slice().str();
}
}
case telegram_api::photoSizeProgressive::ID: {
auto size = move_tl_object_as<telegram_api::photoSizeProgressive>(size_ptr);
if (size->sizes_.empty()) {
LOG(ERROR) << "Receive photo " << id << " from " << source << " with empty size " << to_string(size);
return std::move(res);
}
std::sort(size->sizes_.begin(), size->sizes_.end());
type = std::move(size->type_);
res.dimensions = get_dimensions(size->w_, size->h_, "photoSizeProgressive");
res.size = size->sizes_.back();
size->sizes_.pop_back();
res.progressive_sizes = std::move(size->sizes_);
break;
}
case telegram_api::photoPathSize::ID: {
auto size = move_tl_object_as<telegram_api::photoPathSize>(size_ptr);
if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp) {
LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo " << id << " from " << source << " of format "
<< format;
return std::move(res);
}
return size->bytes_.as_slice().str();
}
default:
UNREACHABLE();
break;
}
if (type.size() != 1) {
LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
res.type = 0;
} else {
res.type = static_cast<uint8>(type[0]);
if (res.type >= 128) {
LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
res.type = 0;
}
}
if (source.get_type() == PhotoSizeSource::Type::Thumbnail) {
source.thumbnail().thumbnail_type = res.type;
}
res.file_id =
register_photo(file_manager, source, id, access_hash, file_reference, owner_dialog_id, res.size, dc_id, format);
if (!content.empty()) {
file_manager->set_content(res.file_id, std::move(content));
}
return std::move(res);
}
AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash,
std::string file_reference, DcId dc_id, DialogId owner_dialog_id,
tl_object_ptr<telegram_api::videoSize> &&size) {
CHECK(size != nullptr);
AnimationSize res;
if (size->type_ != "v" && size->type_ != "u") {
LOG(ERROR) << "Wrong videoSize \"" << size->type_ << "\" in " << to_string(size);
}
res.type = static_cast<uint8>(size->type_[0]);
if (res.type >= 128) {
LOG(ERROR) << "Wrong videoSize \"" << res.type << "\" " << res;
res.type = 0;
}
res.dimensions = get_dimensions(size->w_, size->h_, "get_animation_size");
res.size = size->size_;
if ((size->flags_ & telegram_api::videoSize::VIDEO_START_TS_MASK) != 0) {
res.main_frame_timestamp = size->video_start_ts_;
}
if (source.get_type() == PhotoSizeSource::Type::Thumbnail) {
source.thumbnail().thumbnail_type = res.type;
}
res.file_id = register_photo(file_manager, source, id, access_hash, file_reference, owner_dialog_id, res.size, dc_id,
PhotoFormat::Mpeg4);
return res;
}
PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id,
tl_object_ptr<telegram_api::WebDocument> web_document_ptr) {
if (web_document_ptr == nullptr) {
return {};
}
FileId file_id;
vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
int32 size = 0;
string mime_type;
switch (web_document_ptr->get_id()) {
case telegram_api::webDocument::ID: {
auto web_document = move_tl_object_as<telegram_api::webDocument>(web_document_ptr);
auto r_http_url = parse_url(web_document->url_);
if (r_http_url.is_error()) {
LOG(ERROR) << "Can't parse URL " << web_document->url_;
return {};
}
auto http_url = r_http_url.move_as_ok();
auto url = http_url.get_url();
file_id = file_manager->register_remote(FullRemoteFileLocation(file_type, url, web_document->access_hash_),
FileLocationSource::FromServer, owner_dialog_id, 0, web_document->size_,
get_url_query_file_name(http_url.query_));
size = web_document->size_;
mime_type = std::move(web_document->mime_type_);
attributes = std::move(web_document->attributes_);
break;
}
case telegram_api::webDocumentNoProxy::ID: {
auto web_document = move_tl_object_as<telegram_api::webDocumentNoProxy>(web_document_ptr);
if (web_document->url_.find('.') == string::npos) {
LOG(ERROR) << "Receive invalid URL " << web_document->url_;
return {};
}
auto r_file_id = file_manager->from_persistent_id(web_document->url_, file_type);
if (r_file_id.is_error()) {
LOG(ERROR) << "Can't register URL: " << r_file_id.error();
return {};
}
file_id = r_file_id.move_as_ok();
size = web_document->size_;
mime_type = std::move(web_document->mime_type_);
attributes = std::move(web_document->attributes_);
break;
}
default:
UNREACHABLE();
}
CHECK(file_id.is_valid());
bool is_animation = mime_type == "video/mp4";
bool is_gif = mime_type == "image/gif";
Dimensions dimensions;
for (auto &attribute : attributes) {
switch (attribute->get_id()) {
case telegram_api::documentAttributeImageSize::ID: {
auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
dimensions = get_dimensions(image_size->w_, image_size->h_, "web documentAttributeImageSize");
break;
}
case telegram_api::documentAttributeAnimated::ID:
case telegram_api::documentAttributeHasStickers::ID:
case telegram_api::documentAttributeSticker::ID:
case telegram_api::documentAttributeVideo::ID:
case telegram_api::documentAttributeAudio::ID:
LOG(ERROR) << "Unexpected web document attribute " << to_string(attribute);
break;
case telegram_api::documentAttributeFilename::ID:
break;
default:
UNREACHABLE();
}
}
PhotoSize s;
s.type = is_animation ? 'v' : (is_gif ? 'g' : (file_type == FileType::Thumbnail ? 't' : 'n'));
s.dimensions = dimensions;
s.size = size;
s.file_id = file_id;
return s;
}
td_api::object_ptr<td_api::thumbnail> get_thumbnail_object(FileManager *file_manager, const PhotoSize &photo_size,
PhotoFormat format) {
if (!photo_size.file_id.is_valid()) {
return nullptr;
}
if (format == PhotoFormat::Jpeg && photo_size.type == 'g') {
format = PhotoFormat::Gif;
}
return td_api::make_object<td_api::thumbnail>(get_thumbnail_format_object(format), photo_size.dimensions.width,
photo_size.dimensions.height,
file_manager->get_file_object(photo_size.file_id));
}
static tl_object_ptr<td_api::photoSize> get_photo_size_object(FileManager *file_manager, const PhotoSize *photo_size) {
if (photo_size == nullptr || !photo_size->file_id.is_valid()) {
return nullptr;
}
return td_api::make_object<td_api::photoSize>(
photo_size->type ? std::string(1, static_cast<char>(photo_size->type))
: std::string(), // TODO replace string type with integer type
file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height,
vector<int32>(photo_size->progressive_sizes));
}
static vector<td_api::object_ptr<td_api::photoSize>> get_photo_sizes_object(FileManager *file_manager,
const vector<PhotoSize> &photo_sizes) {
auto sizes = transform(photo_sizes, [file_manager](const PhotoSize &photo_size) {
return get_photo_size_object(file_manager, &photo_size);
});
std::stable_sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) {
if (lhs->photo_->expected_size_ != rhs->photo_->expected_size_) {
return lhs->photo_->expected_size_ < rhs->photo_->expected_size_;
}
return static_cast<uint32>(lhs->width_) * static_cast<uint32>(lhs->height_) <
static_cast<uint32>(rhs->width_) * static_cast<uint32>(rhs->height_);
});
td::remove_if(sizes, [](const auto &size) {
return !size->photo_->local_->can_be_downloaded_ && !size->photo_->local_->is_downloading_completed_;
});
return sizes;
}
bool operator==(const PhotoSize &lhs, const PhotoSize &rhs) {
return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size &&
lhs.file_id == rhs.file_id && lhs.progressive_sizes == rhs.progressive_sizes;
}
bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs) {
return !(lhs == rhs);
}
bool operator<(const PhotoSize &lhs, const PhotoSize &rhs) {
if (lhs.size != rhs.size) {
return lhs.size < rhs.size;
}
auto lhs_pixels = get_pixel_count(lhs.dimensions);
auto rhs_pixels = get_pixel_count(rhs.dimensions);
if (lhs_pixels != rhs_pixels) {
return lhs_pixels < rhs_pixels;
}
int32 lhs_type = lhs.type == 't' ? -1 : lhs.type;
int32 rhs_type = rhs.type == 't' ? -1 : rhs.type;
if (lhs_type != rhs_type) {
return lhs_type < rhs_type;
}
if (lhs.file_id != rhs.file_id) {
return lhs.file_id.get() < rhs.file_id.get();
}
return lhs.dimensions.width < rhs.dimensions.width;
}
StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size) {
return string_builder << "{type = " << photo_size.type << ", dimensions = " << photo_size.dimensions
<< ", size = " << photo_size.size << ", file_id = " << photo_size.file_id
<< ", progressive_sizes = " << photo_size.progressive_sizes << "}";
}
static tl_object_ptr<td_api::animatedChatPhoto> get_animated_chat_photo_object(FileManager *file_manager,
const AnimationSize *animation_size) {
if (animation_size == nullptr || !animation_size->file_id.is_valid()) {
return nullptr;
}
return td_api::make_object<td_api::animatedChatPhoto>(animation_size->dimensions.width,
file_manager->get_file_object(animation_size->file_id),
animation_size->main_frame_timestamp);
}
bool operator==(const AnimationSize &lhs, const AnimationSize &rhs) {
return static_cast<const PhotoSize &>(lhs) == static_cast<const PhotoSize &>(rhs) &&
fabs(lhs.main_frame_timestamp - rhs.main_frame_timestamp) < 1e-3;
}
bool operator!=(const AnimationSize &lhs, const AnimationSize &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const AnimationSize &animation_size) {
return string_builder << static_cast<const PhotoSize &>(animation_size) << " from "
<< animation_size.main_frame_timestamp;
}
Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr<EncryptedFile> &&file,
tl_object_ptr<secret_api::decryptedMessageMediaPhoto> &&photo,
DialogId owner_dialog_id) {
FileId file_id = file_manager->register_remote(
FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::create(file->dc_id_), string()),
FileLocationSource::FromServer, owner_dialog_id, photo->size_, 0,
PSTRING() << static_cast<uint64>(file->id_) << ".jpg");
file_manager->set_encryption_key(file_id, FileEncryptionKey{photo->key_.as_slice(), photo->iv_.as_slice()});
Photo res;
res.id = 0;
res.date = 0;
if (!photo->thumb_.empty()) {
res.photos.push_back(get_secret_thumbnail_photo_size(file_manager, std::move(photo->thumb_), owner_dialog_id,
photo->thumb_w_, photo->thumb_h_));
}
PhotoSize s;
s.type = 'i';
s.dimensions = get_dimensions(photo->w_, photo->h_, "get_encrypted_file_photo");
s.size = photo->size_;
s.file_id = file_id;
res.photos.push_back(s);
return res;
}
Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::Photo> &&photo, DialogId owner_dialog_id) {
if (photo == nullptr || photo->get_id() == telegram_api::photoEmpty::ID) {
return Photo();
}
CHECK(photo->get_id() == telegram_api::photo::ID);
return get_photo(file_manager, move_tl_object_as<telegram_api::photo>(photo), owner_dialog_id);
}
Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::photo> &&photo, DialogId owner_dialog_id) {
CHECK(photo != nullptr);
Photo res;
res.id = photo->id_;
res.date = photo->date_;
res.has_stickers = (photo->flags_ & telegram_api::photo::HAS_STICKERS_MASK) != 0;
if (res.is_empty()) {
LOG(ERROR) << "Receive photo with identifier " << res.id.get();
res.id = -3;
}
DcId dc_id = DcId::create(photo->dc_id_);
for (auto &size_ptr : photo->sizes_) {
auto photo_size = get_photo_size(file_manager, {FileType::Photo, 0}, photo->id_, photo->access_hash_,
photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id,
std::move(size_ptr), PhotoFormat::Jpeg);
if (photo_size.get_offset() == 0) {
PhotoSize &size = photo_size.get<0>();
if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'u' || size.type == 'v') {
LOG(ERROR) << "Skip unallowed photo size " << size;
continue;
}
res.photos.push_back(std::move(size));
} else {
if (G()->shared_config().get_option_boolean("disable_minithumbnails")) {
res.minithumbnail = "";
} else {
res.minithumbnail = std::move(photo_size.get<1>());
}
}
}
for (auto &size_ptr : photo->video_sizes_) {
auto animation =
get_animation_size(file_manager, {FileType::Photo, 0}, photo->id_, photo->access_hash_,
photo->file_reference_.as_slice().str(), dc_id, owner_dialog_id, std::move(size_ptr));
if (animation.type != 0 && animation.dimensions.width == animation.dimensions.height) {
res.animations.push_back(std::move(animation));
}
}
return res;
}
Photo get_web_document_photo(FileManager *file_manager, tl_object_ptr<telegram_api::WebDocument> web_document,
DialogId owner_dialog_id) {
PhotoSize s = get_web_document_photo_size(file_manager, FileType::Photo, owner_dialog_id, std::move(web_document));
Photo photo;
if (s.file_id.is_valid() && s.type != 'v' && s.type != 'g') {
photo.id = 0;
photo.photos.push_back(s);
}
return photo;
}
tl_object_ptr<td_api::photo> get_photo_object(FileManager *file_manager, const Photo &photo) {
if (photo.is_empty()) {
return nullptr;
}
return td_api::make_object<td_api::photo>(photo.has_stickers, get_minithumbnail_object(photo.minithumbnail),
get_photo_sizes_object(file_manager, photo.photos));
}
tl_object_ptr<td_api::chatPhoto> get_chat_photo_object(FileManager *file_manager, const Photo &photo) {
if (photo.is_empty()) {
return nullptr;
}
const AnimationSize *animation = photo.animations.empty() ? nullptr : &photo.animations.back();
return td_api::make_object<td_api::chatPhoto>(
photo.id.get(), photo.date, get_minithumbnail_object(photo.minithumbnail),
get_photo_sizes_object(file_manager, photo.photos), get_animated_chat_photo_object(file_manager, animation));
}
void photo_delete_thumbnail(Photo &photo) {
for (size_t i = 0; i < photo.photos.size(); i++) {
if (photo.photos[i].type == 't') {
photo.photos.erase(photo.photos.begin() + i);
return;
}
}
}
bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret, bool is_bot) {
if (photo.photos.empty() || photo.photos.back().type != 'i') {
LOG(ERROR) << "Wrong photo: " << photo;
return false;
}
auto file_id = photo.photos.back().file_id;
auto file_view = file_manager->get_file_view(file_id);
if (is_secret) {
if (!file_view.is_encrypted_secret() || !file_view.has_remote_location()) {
return false;
}
for (const auto &size : photo.photos) {
if (size.type == 't' && size.file_id.is_valid()) {
return false;
}
}
return true;
} else {
if (file_view.is_encrypted()) {
return false;
}
if (is_bot && file_view.has_remote_location()) {
return true;
}
return /* file_view.has_remote_location() || */ file_view.has_url();
}
}
tl_object_ptr<telegram_api::InputMedia> photo_get_input_media(FileManager *file_manager, const Photo &photo,
tl_object_ptr<telegram_api::InputFile> input_file,
int32 ttl) {
if (!photo.photos.empty()) {
auto file_id = photo.photos.back().file_id;
auto file_view = file_manager->get_file_view(file_id);
if (file_view.is_encrypted()) {
return nullptr;
}
if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
int32 flags = 0;
if (ttl != 0) {
flags |= telegram_api::inputMediaPhoto::TTL_SECONDS_MASK;
}
return make_tl_object<telegram_api::inputMediaPhoto>(flags, file_view.main_remote_location().as_input_photo(),
ttl);
}
if (file_view.has_url()) {
int32 flags = 0;
if (ttl != 0) {
flags |= telegram_api::inputMediaPhotoExternal::TTL_SECONDS_MASK;
}
LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and TTL " << ttl;
return make_tl_object<telegram_api::inputMediaPhotoExternal>(flags, file_view.url(), ttl);
}
if (input_file == nullptr) {
CHECK(!file_view.has_remote_location());
}
}
if (input_file != nullptr) {
int32 flags = 0;
vector<tl_object_ptr<telegram_api::InputDocument>> added_stickers;
if (photo.has_stickers) {
flags |= telegram_api::inputMediaUploadedPhoto::STICKERS_MASK;
added_stickers = file_manager->get_input_documents(photo.sticker_file_ids);
}
if (ttl != 0) {
flags |= telegram_api::inputMediaUploadedPhoto::TTL_SECONDS_MASK;
}
return make_tl_object<telegram_api::inputMediaUploadedPhoto>(flags, std::move(input_file),
std::move(added_stickers), ttl);
}
return nullptr;
}
SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const Photo &photo,
tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
const string &caption, BufferSlice thumbnail) {
FileId file_id;
int32 width = 0;
int32 height = 0;
FileId thumbnail_file_id;
int32 thumbnail_width = 0;
int32 thumbnail_height = 0;
for (const auto &size : photo.photos) {
if (size.type == 'i') {
file_id = size.file_id;
width = size.dimensions.width;
height = size.dimensions.height;
}
if (size.type == 't') {
thumbnail_file_id = size.file_id;
thumbnail_width = size.dimensions.width;
thumbnail_height = size.dimensions.height;
}
}
if (file_id.empty()) {
LOG(ERROR) << "NO SIZE";
return {};
}
auto file_view = file_manager->get_file_view(file_id);
auto &encryption_key = file_view.encryption_key();
if (!file_view.is_encrypted_secret() || encryption_key.empty()) {
return {};
}
if (file_view.has_remote_location()) {
LOG(INFO) << "Photo has remote location";
input_file = file_view.main_remote_location().as_input_encrypted_file();
}
if (input_file == nullptr) {
return {};
}
if (thumbnail_file_id.is_valid() && thumbnail.empty()) {
return {};
}
return SecretInputMedia{
std::move(input_file),
make_tl_object<secret_api::decryptedMessageMediaPhoto>(
std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, narrow_cast<int32>(file_view.size()),
BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), caption)};
}
vector<FileId> photo_get_file_ids(const Photo &photo) {
auto result = transform(photo.photos, [](auto &size) { return size.file_id; });
if (!photo.animations.empty()) {
// photo file IDs must be first
append(result, transform(photo.animations, [](auto &size) { return size.file_id; }));
}
return result;
}
bool operator==(const Photo &lhs, const Photo &rhs) {
return lhs.id.get() == rhs.id.get() && lhs.photos == rhs.photos && lhs.animations == rhs.animations;
}
bool operator!=(const Photo &lhs, const Photo &rhs) {
return !(lhs == rhs);
}
StringBuilder &operator<<(StringBuilder &string_builder, const Photo &photo) {
string_builder << "[ID = " << photo.id.get() << ", photos = " << format::as_array(photo.photos);
if (!photo.animations.empty()) {
string_builder << ", animations = " << format::as_array(photo.animations);
}
return string_builder << ']';
}
tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
const tl_object_ptr<telegram_api::photo> &photo) {
if (photo == nullptr) {
return nullptr;
}
bool have_photo_small = false;
bool have_photo_big = false;
for (auto &size_ptr : photo->sizes_) {
switch (size_ptr->get_id()) {
case telegram_api::photoSizeEmpty::ID:
break;
case telegram_api::photoSize::ID: {
auto size = static_cast<const telegram_api::photoSize *>(size_ptr.get());
if (size->type_ == "a") {
have_photo_small = true;
} else if (size->type_ == "c") {
have_photo_big = true;
}
break;
}
case telegram_api::photoCachedSize::ID: {
auto size = static_cast<const telegram_api::photoCachedSize *>(size_ptr.get());
if (size->type_ == "a") {
have_photo_small = true;
} else if (size->type_ == "c") {
have_photo_big = true;
}
break;
}
case telegram_api::photoStrippedSize::ID:
break;
case telegram_api::photoSizeProgressive::ID: {
auto size = static_cast<const telegram_api::photoSizeProgressive *>(size_ptr.get());
if (size->type_ == "a") {
have_photo_small = true;
} else if (size->type_ == "c") {
have_photo_big = true;
}
break;
}
default:
UNREACHABLE();
break;
}
}
if (!have_photo_small || !have_photo_big) {
return nullptr;
}
int32 flags = 0;
if (!photo->video_sizes_.empty()) {
flags |= telegram_api::userProfilePhoto::HAS_VIDEO_MASK;
}
return make_tl_object<telegram_api::userProfilePhoto>(flags, false /*ignored*/, photo->id_, BufferSlice(),
photo->dc_id_);
}
} // namespace td