1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/telegram/AnimationsManager.h"
8 
9 #include "td/telegram/AuthManager.h"
10 #include "td/telegram/ConfigShared.h"
11 #include "td/telegram/DialogId.h"
12 #include "td/telegram/Document.h"
13 #include "td/telegram/DocumentsManager.h"
14 #include "td/telegram/FileReferenceManager.h"
15 #include "td/telegram/files/FileManager.h"
16 #include "td/telegram/files/FileType.h"
17 #include "td/telegram/Global.h"
18 #include "td/telegram/logevent/LogEvent.h"
19 #include "td/telegram/misc.h"
20 #include "td/telegram/secret_api.h"
21 #include "td/telegram/Td.h"
22 #include "td/telegram/td_api.h"
23 #include "td/telegram/TdDb.h"
24 #include "td/telegram/TdParameters.h"
25 #include "td/telegram/telegram_api.h"
26 
27 #include "td/db/SqliteKeyValueAsync.h"
28 
29 #include "td/actor/PromiseFuture.h"
30 
31 #include "td/utils/algorithm.h"
32 #include "td/utils/logging.h"
33 #include "td/utils/misc.h"
34 #include "td/utils/Random.h"
35 #include "td/utils/Time.h"
36 #include "td/utils/tl_helpers.h"
37 
38 #include <algorithm>
39 
40 namespace td {
41 
42 class GetSavedGifsQuery final : public Td::ResultHandler {
43   bool is_repair_ = false;
44 
45  public:
send(bool is_repair,int64 hash)46   void send(bool is_repair, int64 hash) {
47     is_repair_ = is_repair;
48     send_query(G()->net_query_creator().create(telegram_api::messages_getSavedGifs(hash)));
49   }
50 
on_result(BufferSlice packet)51   void on_result(BufferSlice packet) final {
52     auto result_ptr = fetch_result<telegram_api::messages_getSavedGifs>(packet);
53     if (result_ptr.is_error()) {
54       return on_error(result_ptr.move_as_error());
55     }
56 
57     auto ptr = result_ptr.move_as_ok();
58     td_->animations_manager_->on_get_saved_animations(is_repair_, std::move(ptr));
59   }
60 
on_error(Status status)61   void on_error(Status status) final {
62     if (!G()->is_expected_error(status)) {
63       LOG(ERROR) << "Receive error for get saved animations: " << status;
64     }
65     td_->animations_manager_->on_get_saved_animations_failed(is_repair_, std::move(status));
66   }
67 };
68 
69 class SaveGifQuery final : public Td::ResultHandler {
70   FileId file_id_;
71   string file_reference_;
72   bool unsave_ = false;
73 
74   Promise<Unit> promise_;
75 
76  public:
SaveGifQuery(Promise<Unit> && promise)77   explicit SaveGifQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
78   }
79 
send(FileId file_id,tl_object_ptr<telegram_api::inputDocument> && input_document,bool unsave)80   void send(FileId file_id, tl_object_ptr<telegram_api::inputDocument> &&input_document, bool unsave) {
81     CHECK(input_document != nullptr);
82     CHECK(file_id.is_valid());
83     file_id_ = file_id;
84     file_reference_ = input_document->file_reference_.as_slice().str();
85     unsave_ = unsave;
86     send_query(G()->net_query_creator().create(telegram_api::messages_saveGif(std::move(input_document), unsave)));
87   }
88 
on_result(BufferSlice packet)89   void on_result(BufferSlice packet) final {
90     auto result_ptr = fetch_result<telegram_api::messages_saveGif>(packet);
91     if (result_ptr.is_error()) {
92       return on_error(result_ptr.move_as_error());
93     }
94 
95     bool result = result_ptr.move_as_ok();
96     LOG(INFO) << "Receive result for save GIF: " << result;
97     if (!result) {
98       td_->animations_manager_->reload_saved_animations(true);
99     }
100 
101     promise_.set_value(Unit());
102   }
103 
on_error(Status status)104   void on_error(Status status) final {
105     if (!td_->auth_manager_->is_bot() && FileReferenceManager::is_file_reference_error(status)) {
106       VLOG(file_references) << "Receive " << status << " for " << file_id_;
107       td_->file_manager_->delete_file_reference(file_id_, file_reference_);
108       td_->file_reference_manager_->repair_file_reference(
109           file_id_, PromiseCreator::lambda([animation_id = file_id_, unsave = unsave_,
110                                             promise = std::move(promise_)](Result<Unit> result) mutable {
111             if (result.is_error()) {
112               return promise.set_error(Status::Error(400, "Failed to find the animation"));
113             }
114 
115             send_closure(G()->animations_manager(), &AnimationsManager::send_save_gif_query, animation_id, unsave,
116                          std::move(promise));
117           }));
118       return;
119     }
120 
121     if (!G()->is_expected_error(status)) {
122       LOG(ERROR) << "Receive error for save GIF: " << status;
123     }
124     td_->animations_manager_->reload_saved_animations(true);
125     promise_.set_error(std::move(status));
126   }
127 };
128 
AnimationsManager(Td * td,ActorShared<> parent)129 AnimationsManager::AnimationsManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
130   auto limit_string = G()->td_db()->get_binlog_pmc()->get("saved_animations_limit");
131   if (!limit_string.empty()) {
132     auto new_limit = to_integer<int32>(limit_string);
133     if (new_limit > 0) {
134       LOG(DEBUG) << "Load saved animations limit = " << new_limit;
135       saved_animations_limit_ = new_limit;
136     } else {
137       LOG(ERROR) << "Wrong saved animations limit = \"" << limit_string << "\" stored in database";
138     }
139   }
140   next_saved_animations_load_time_ = Time::now();
141 }
142 
tear_down()143 void AnimationsManager::tear_down() {
144   parent_.reset();
145 }
146 
get_animation_duration(FileId file_id) const147 int32 AnimationsManager::get_animation_duration(FileId file_id) const {
148   auto it = animations_.find(file_id);
149   CHECK(it != animations_.end());
150   return it->second->duration;
151 }
152 
get_animation_object(FileId file_id) const153 tl_object_ptr<td_api::animation> AnimationsManager::get_animation_object(FileId file_id) const {
154   if (!file_id.is_valid()) {
155     return nullptr;
156   }
157 
158   auto it = animations_.find(file_id);
159   CHECK(it != animations_.end());
160   auto animation = it->second.get();
161   CHECK(animation != nullptr);
162   auto thumbnail =
163       animation->animated_thumbnail.file_id.is_valid()
164           ? get_thumbnail_object(td_->file_manager_.get(), animation->animated_thumbnail, PhotoFormat::Mpeg4)
165           : get_thumbnail_object(td_->file_manager_.get(), animation->thumbnail, PhotoFormat::Jpeg);
166   return make_tl_object<td_api::animation>(animation->duration, animation->dimensions.width,
167                                            animation->dimensions.height, animation->file_name, animation->mime_type,
168                                            animation->has_stickers, get_minithumbnail_object(animation->minithumbnail),
169                                            std::move(thumbnail), td_->file_manager_->get_file_object(file_id));
170 }
171 
on_get_animation(unique_ptr<Animation> new_animation,bool replace)172 FileId AnimationsManager::on_get_animation(unique_ptr<Animation> new_animation, bool replace) {
173   auto file_id = new_animation->file_id;
174   CHECK(file_id.is_valid());
175   auto &a = animations_[file_id];
176   LOG(INFO) << (a == nullptr ? "Add" : (replace ? "Replace" : "Ignore")) << " animation " << file_id << " of size "
177             << new_animation->dimensions;
178   if (a == nullptr) {
179     a = std::move(new_animation);
180   } else if (replace) {
181     CHECK(a->file_id == file_id);
182     if (a->mime_type != new_animation->mime_type) {
183       LOG(DEBUG) << "Animation " << file_id << " info has changed";
184       a->mime_type = new_animation->mime_type;
185     }
186     if (a->file_name != new_animation->file_name) {
187       LOG(DEBUG) << "Animation " << file_id << " file name has changed";
188       a->file_name = std::move(new_animation->file_name);
189     }
190     if (a->dimensions != new_animation->dimensions) {
191       LOG(DEBUG) << "Animation " << file_id << " dimensions have changed";
192       a->dimensions = new_animation->dimensions;
193     }
194     if (a->duration != new_animation->duration) {
195       LOG(DEBUG) << "Animation " << file_id << " duration has changed";
196       a->duration = new_animation->duration;
197     }
198     if (a->minithumbnail != new_animation->minithumbnail) {
199       a->minithumbnail = std::move(new_animation->minithumbnail);
200     }
201     if (a->thumbnail != new_animation->thumbnail) {
202       if (!a->thumbnail.file_id.is_valid()) {
203         LOG(DEBUG) << "Animation " << file_id << " thumbnail has changed";
204       } else {
205         LOG(INFO) << "Animation " << file_id << " thumbnail has changed from " << a->thumbnail << " to "
206                   << new_animation->thumbnail;
207       }
208       a->thumbnail = new_animation->thumbnail;
209     }
210     if (a->animated_thumbnail != new_animation->animated_thumbnail) {
211       if (!a->animated_thumbnail.file_id.is_valid()) {
212         LOG(DEBUG) << "Animation " << file_id << " animated thumbnail has changed";
213       } else {
214         LOG(INFO) << "Animation " << file_id << " animated thumbnail has changed from " << a->animated_thumbnail
215                   << " to " << new_animation->animated_thumbnail;
216       }
217       a->animated_thumbnail = new_animation->animated_thumbnail;
218     }
219     if (a->has_stickers != new_animation->has_stickers && new_animation->has_stickers) {
220       a->has_stickers = new_animation->has_stickers;
221     }
222     if (a->sticker_file_ids != new_animation->sticker_file_ids && !new_animation->sticker_file_ids.empty()) {
223       a->sticker_file_ids = std::move(new_animation->sticker_file_ids);
224     }
225   }
226 
227   return file_id;
228 }
229 
get_animation(FileId file_id) const230 const AnimationsManager::Animation *AnimationsManager::get_animation(FileId file_id) const {
231   auto animation = animations_.find(file_id);
232   if (animation == animations_.end()) {
233     return nullptr;
234   }
235 
236   CHECK(animation->second->file_id == file_id);
237   return animation->second.get();
238 }
239 
get_animation_thumbnail_file_id(FileId file_id) const240 FileId AnimationsManager::get_animation_thumbnail_file_id(FileId file_id) const {
241   auto animation = get_animation(file_id);
242   CHECK(animation != nullptr);
243   return animation->thumbnail.file_id;
244 }
245 
get_animation_animated_thumbnail_file_id(FileId file_id) const246 FileId AnimationsManager::get_animation_animated_thumbnail_file_id(FileId file_id) const {
247   auto animation = get_animation(file_id);
248   CHECK(animation != nullptr);
249   return animation->animated_thumbnail.file_id;
250 }
251 
delete_animation_thumbnail(FileId file_id)252 void AnimationsManager::delete_animation_thumbnail(FileId file_id) {
253   auto &animation = animations_[file_id];
254   CHECK(animation != nullptr);
255   animation->thumbnail = PhotoSize();
256   animation->animated_thumbnail = AnimationSize();
257 }
258 
dup_animation(FileId new_id,FileId old_id)259 FileId AnimationsManager::dup_animation(FileId new_id, FileId old_id) {
260   LOG(INFO) << "Dup animation " << old_id << " to " << new_id;
261   const Animation *old_animation = get_animation(old_id);
262   CHECK(old_animation != nullptr);
263   auto &new_animation = animations_[new_id];
264   CHECK(!new_animation);
265   new_animation = make_unique<Animation>(*old_animation);
266   new_animation->file_id = new_id;
267   new_animation->thumbnail.file_id = td_->file_manager_->dup_file_id(new_animation->thumbnail.file_id);
268   new_animation->animated_thumbnail.file_id =
269       td_->file_manager_->dup_file_id(new_animation->animated_thumbnail.file_id);
270   return new_id;
271 }
272 
merge_animations(FileId new_id,FileId old_id,bool can_delete_old)273 void AnimationsManager::merge_animations(FileId new_id, FileId old_id, bool can_delete_old) {
274   CHECK(old_id.is_valid() && new_id.is_valid());
275   CHECK(new_id != old_id);
276 
277   LOG(INFO) << "Merge animations " << new_id << " and " << old_id;
278   const Animation *old_ = get_animation(old_id);
279   CHECK(old_ != nullptr);
280 
281   bool need_merge = true;
282   auto new_it = animations_.find(new_id);
283   if (new_it == animations_.end()) {
284     auto &old = animations_[old_id];
285     if (!can_delete_old) {
286       dup_animation(new_id, old_id);
287     } else {
288       old->file_id = new_id;
289       animations_.emplace(new_id, std::move(old));
290     }
291   } else {
292     Animation *new_ = new_it->second.get();
293     CHECK(new_ != nullptr);
294 
295     if (old_->thumbnail != new_->thumbnail) {
296       //    LOG_STATUS(td_->file_manager_->merge(new_->thumbnail.file_id, old_->thumbnail.file_id));
297     }
298     if (new_->file_name.size() == old_->file_name.size() + 4 && new_->file_name == old_->file_name + ".mp4") {
299       need_merge = false;
300     }
301   }
302   if (need_merge) {
303     LOG_STATUS(td_->file_manager_->merge(new_id, old_id));
304   }
305   if (can_delete_old) {
306     animations_.erase(old_id);
307   }
308 }
309 
create_animation(FileId file_id,string minithumbnail,PhotoSize thumbnail,AnimationSize animated_thumbnail,bool has_stickers,vector<FileId> && sticker_file_ids,string file_name,string mime_type,int32 duration,Dimensions dimensions,bool replace)310 void AnimationsManager::create_animation(FileId file_id, string minithumbnail, PhotoSize thumbnail,
311                                          AnimationSize animated_thumbnail, bool has_stickers,
312                                          vector<FileId> &&sticker_file_ids, string file_name, string mime_type,
313                                          int32 duration, Dimensions dimensions, bool replace) {
314   auto a = make_unique<Animation>();
315   a->file_id = file_id;
316   a->file_name = std::move(file_name);
317   a->mime_type = std::move(mime_type);
318   a->duration = max(duration, 0);
319   a->dimensions = dimensions;
320   if (!td_->auth_manager_->is_bot()) {
321     a->minithumbnail = std::move(minithumbnail);
322   }
323   a->thumbnail = std::move(thumbnail);
324   a->animated_thumbnail = std::move(animated_thumbnail);
325   a->has_stickers = has_stickers;
326   a->sticker_file_ids = std::move(sticker_file_ids);
327   on_get_animation(std::move(a), replace);
328 }
329 
get_input_media(FileId file_id,tl_object_ptr<telegram_api::InputFile> input_file,tl_object_ptr<telegram_api::InputFile> input_thumbnail) const330 tl_object_ptr<telegram_api::InputMedia> AnimationsManager::get_input_media(
331     FileId file_id, tl_object_ptr<telegram_api::InputFile> input_file,
332     tl_object_ptr<telegram_api::InputFile> input_thumbnail) const {
333   auto file_view = td_->file_manager_->get_file_view(file_id);
334   if (file_view.is_encrypted()) {
335     return nullptr;
336   }
337   if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
338     return make_tl_object<telegram_api::inputMediaDocument>(0, file_view.main_remote_location().as_input_document(), 0,
339                                                             string());
340   }
341   if (file_view.has_url()) {
342     return make_tl_object<telegram_api::inputMediaDocumentExternal>(0, file_view.url(), 0);
343   }
344 
345   if (input_file != nullptr) {
346     const Animation *animation = get_animation(file_id);
347     CHECK(animation != nullptr);
348 
349     vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
350     if (!animation->file_name.empty()) {
351       attributes.push_back(make_tl_object<telegram_api::documentAttributeFilename>(animation->file_name));
352     }
353     string mime_type = animation->mime_type;
354     if (mime_type == "video/mp4") {
355       attributes.push_back(make_tl_object<telegram_api::documentAttributeVideo>(
356           0, false /*ignored*/, false /*ignored*/, animation->duration, animation->dimensions.width,
357           animation->dimensions.height));
358     } else if (animation->dimensions.width != 0 && animation->dimensions.height != 0) {
359       if (!begins_with(mime_type, "image/")) {
360         mime_type = "image/gif";
361       }
362       attributes.push_back(make_tl_object<telegram_api::documentAttributeImageSize>(animation->dimensions.width,
363                                                                                     animation->dimensions.height));
364     }
365     int32 flags = 0;
366     vector<tl_object_ptr<telegram_api::InputDocument>> added_stickers;
367     if (animation->has_stickers) {
368       flags |= telegram_api::inputMediaUploadedDocument::STICKERS_MASK;
369       added_stickers = td_->file_manager_->get_input_documents(animation->sticker_file_ids);
370     }
371     if (input_thumbnail != nullptr) {
372       flags |= telegram_api::inputMediaUploadedDocument::THUMB_MASK;
373     }
374     return make_tl_object<telegram_api::inputMediaUploadedDocument>(
375         flags, false /*ignored*/, false /*ignored*/, std::move(input_file), std::move(input_thumbnail), mime_type,
376         std::move(attributes), std::move(added_stickers), 0);
377   } else {
378     CHECK(!file_view.has_remote_location());
379   }
380 
381   return nullptr;
382 }
383 
get_secret_input_media(FileId animation_file_id,tl_object_ptr<telegram_api::InputEncryptedFile> input_file,const string & caption,BufferSlice thumbnail) const384 SecretInputMedia AnimationsManager::get_secret_input_media(FileId animation_file_id,
385                                                            tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
386                                                            const string &caption, BufferSlice thumbnail) const {
387   auto *animation = get_animation(animation_file_id);
388   CHECK(animation != nullptr);
389   auto file_view = td_->file_manager_->get_file_view(animation_file_id);
390   auto &encryption_key = file_view.encryption_key();
391   if (!file_view.is_encrypted_secret() || encryption_key.empty()) {
392     return SecretInputMedia{};
393   }
394   if (file_view.has_remote_location()) {
395     input_file = file_view.main_remote_location().as_input_encrypted_file();
396   }
397   if (!input_file) {
398     return SecretInputMedia{};
399   }
400   if (animation->thumbnail.file_id.is_valid() && thumbnail.empty()) {
401     return SecretInputMedia{};
402   }
403   vector<tl_object_ptr<secret_api::DocumentAttribute>> attributes;
404   if (!animation->file_name.empty()) {
405     attributes.push_back(make_tl_object<secret_api::documentAttributeFilename>(animation->file_name));
406   }
407   if (animation->duration != 0 && animation->mime_type == "video/mp4") {
408     attributes.push_back(make_tl_object<secret_api::documentAttributeVideo66>(
409         0, false, animation->duration, animation->dimensions.width, animation->dimensions.height));
410   }
411   if (animation->dimensions.width != 0 && animation->dimensions.height != 0) {
412     attributes.push_back(make_tl_object<secret_api::documentAttributeImageSize>(animation->dimensions.width,
413                                                                                 animation->dimensions.height));
414   }
415   attributes.push_back(make_tl_object<secret_api::documentAttributeAnimated>());
416 
417   return SecretInputMedia{
418       std::move(input_file),
419       make_tl_object<secret_api::decryptedMessageMediaDocument>(
420           std::move(thumbnail), animation->thumbnail.dimensions.width, animation->thumbnail.dimensions.height,
421           animation->mime_type, narrow_cast<int32>(file_view.size()), BufferSlice(encryption_key.key_slice()),
422           BufferSlice(encryption_key.iv_slice()), std::move(attributes), caption)};
423 }
424 
on_update_animation_search_emojis(string animation_search_emojis)425 void AnimationsManager::on_update_animation_search_emojis(string animation_search_emojis) {
426   if (G()->close_flag()) {
427     return;
428   }
429   if (td_->auth_manager_->is_bot()) {
430     G()->shared_config().set_option_empty("animation_search_emojis");
431     return;
432   }
433 
434   is_animation_search_emojis_inited_ = true;
435   if (animation_search_emojis_ == animation_search_emojis) {
436     return;
437   }
438   animation_search_emojis_ = std::move(animation_search_emojis);
439 
440   try_send_update_animation_search_parameters();
441 }
442 
on_update_animation_search_provider(string animation_search_provider)443 void AnimationsManager::on_update_animation_search_provider(string animation_search_provider) {
444   if (G()->close_flag()) {
445     return;
446   }
447   if (td_->auth_manager_->is_bot()) {
448     G()->shared_config().set_option_empty("animation_search_provider");
449     return;
450   }
451 
452   is_animation_search_provider_inited_ = true;
453   if (animation_search_provider_ == animation_search_provider) {
454     return;
455   }
456   animation_search_provider_ = std::move(animation_search_provider);
457 
458   try_send_update_animation_search_parameters();
459 }
460 
on_update_saved_animations_limit(int32 saved_animations_limit)461 void AnimationsManager::on_update_saved_animations_limit(int32 saved_animations_limit) {
462   if (saved_animations_limit != saved_animations_limit_) {
463     if (saved_animations_limit > 0) {
464       LOG(INFO) << "Update saved animations limit to " << saved_animations_limit;
465       G()->td_db()->get_binlog_pmc()->set("saved_animations_limit", to_string(saved_animations_limit));
466       saved_animations_limit_ = saved_animations_limit;
467       if (static_cast<int32>(saved_animation_ids_.size()) > saved_animations_limit_) {
468         saved_animation_ids_.resize(saved_animations_limit_);
469         send_update_saved_animations();
470       }
471     } else {
472       LOG(ERROR) << "Receive wrong saved animations limit = " << saved_animations_limit;
473     }
474   }
475 }
476 
477 class AnimationsManager::AnimationListLogEvent {
478  public:
479   vector<FileId> animation_ids;
480 
481   AnimationListLogEvent() = default;
482 
AnimationListLogEvent(vector<FileId> animation_ids)483   explicit AnimationListLogEvent(vector<FileId> animation_ids) : animation_ids(std::move(animation_ids)) {
484   }
485 
486   template <class StorerT>
store(StorerT & storer) const487   void store(StorerT &storer) const {
488     AnimationsManager *animations_manager = storer.context()->td().get_actor_unsafe()->animations_manager_.get();
489     td::store(narrow_cast<int32>(animation_ids.size()), storer);
490     for (auto animation_id : animation_ids) {
491       animations_manager->store_animation(animation_id, storer);
492     }
493   }
494 
495   template <class ParserT>
parse(ParserT & parser)496   void parse(ParserT &parser) {
497     AnimationsManager *animations_manager = parser.context()->td().get_actor_unsafe()->animations_manager_.get();
498     int32 size = parser.fetch_int();
499     animation_ids.resize(size);
500     for (auto &animation_id : animation_ids) {
501       animation_id = animations_manager->parse_animation(parser);
502     }
503   }
504 };
505 
reload_saved_animations(bool force)506 void AnimationsManager::reload_saved_animations(bool force) {
507   if (G()->close_flag()) {
508     return;
509   }
510 
511   if (!td_->auth_manager_->is_bot() && !are_saved_animations_being_loaded_ &&
512       (next_saved_animations_load_time_ < Time::now() || force)) {
513     LOG_IF(INFO, force) << "Reload saved animations";
514     are_saved_animations_being_loaded_ = true;
515     td_->create_handler<GetSavedGifsQuery>()->send(false, get_saved_animations_hash("reload_saved_animations"));
516   }
517 }
518 
repair_saved_animations(Promise<Unit> && promise)519 void AnimationsManager::repair_saved_animations(Promise<Unit> &&promise) {
520   if (td_->auth_manager_->is_bot()) {
521     return promise.set_error(Status::Error(400, "Bots have no saved animations"));
522   }
523 
524   repair_saved_animations_queries_.push_back(std::move(promise));
525   if (repair_saved_animations_queries_.size() == 1u) {
526     td_->create_handler<GetSavedGifsQuery>()->send(true, 0);
527   }
528 }
529 
get_saved_animations(Promise<Unit> && promise)530 vector<FileId> AnimationsManager::get_saved_animations(Promise<Unit> &&promise) {
531   if (!are_saved_animations_loaded_) {
532     load_saved_animations(std::move(promise));
533     return {};
534   }
535   reload_saved_animations(false);
536 
537   promise.set_value(Unit());
538   return saved_animation_ids_;
539 }
540 
load_saved_animations(Promise<Unit> && promise)541 void AnimationsManager::load_saved_animations(Promise<Unit> &&promise) {
542   if (td_->auth_manager_->is_bot()) {
543     are_saved_animations_loaded_ = true;
544   }
545   if (are_saved_animations_loaded_) {
546     promise.set_value(Unit());
547     return;
548   }
549   load_saved_animations_queries_.push_back(std::move(promise));
550   if (load_saved_animations_queries_.size() == 1u) {
551     if (G()->parameters().use_file_db) {  // otherwise there is no sqlite_pmc, TODO
552       LOG(INFO) << "Trying to load saved animations from database";
553       G()->td_db()->get_sqlite_pmc()->get("ans", PromiseCreator::lambda([](string value) {
554                                             send_closure(G()->animations_manager(),
555                                                          &AnimationsManager::on_load_saved_animations_from_database,
556                                                          std::move(value));
557                                           }));
558     } else {
559       LOG(INFO) << "Trying to load saved animations from server";
560       reload_saved_animations(true);
561     }
562   }
563 }
564 
on_load_saved_animations_from_database(const string & value)565 void AnimationsManager::on_load_saved_animations_from_database(const string &value) {
566   if (G()->close_flag()) {
567     return;
568   }
569   if (value.empty()) {
570     LOG(INFO) << "Saved animations aren't found in database";
571     reload_saved_animations(true);
572     return;
573   }
574 
575   LOG(INFO) << "Successfully loaded saved animations list of size " << value.size() << " from database";
576 
577   AnimationListLogEvent log_event;
578   log_event_parse(log_event, value).ensure();
579 
580   on_load_saved_animations_finished(std::move(log_event.animation_ids), true);
581 }
582 
on_load_saved_animations_finished(vector<FileId> && saved_animation_ids,bool from_database)583 void AnimationsManager::on_load_saved_animations_finished(vector<FileId> &&saved_animation_ids, bool from_database) {
584   if (static_cast<int32>(saved_animation_ids.size()) > saved_animations_limit_) {
585     saved_animation_ids.resize(saved_animations_limit_);
586   }
587   saved_animation_ids_ = std::move(saved_animation_ids);
588   are_saved_animations_loaded_ = true;
589   send_update_saved_animations(from_database);
590   auto promises = std::move(load_saved_animations_queries_);
591   load_saved_animations_queries_.clear();
592   for (auto &promise : promises) {
593     promise.set_value(Unit());
594   }
595 }
596 
on_get_saved_animations(bool is_repair,tl_object_ptr<telegram_api::messages_SavedGifs> && saved_animations_ptr)597 void AnimationsManager::on_get_saved_animations(
598     bool is_repair, tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr) {
599   CHECK(!td_->auth_manager_->is_bot());
600   if (!is_repair) {
601     are_saved_animations_being_loaded_ = false;
602     next_saved_animations_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
603   }
604 
605   CHECK(saved_animations_ptr != nullptr);
606   int32 constructor_id = saved_animations_ptr->get_id();
607   if (constructor_id == telegram_api::messages_savedGifsNotModified::ID) {
608     if (is_repair) {
609       return on_get_saved_animations_failed(true, Status::Error(500, "Failed to reload saved animations"));
610     }
611     LOG(INFO) << "Saved animations are not modified";
612     return;
613   }
614   CHECK(constructor_id == telegram_api::messages_savedGifs::ID);
615   auto saved_animations = move_tl_object_as<telegram_api::messages_savedGifs>(saved_animations_ptr);
616   LOG(INFO) << "Receive " << saved_animations->gifs_.size() << " saved animations from server";
617 
618   vector<FileId> saved_animation_ids;
619   saved_animation_ids.reserve(saved_animations->gifs_.size());
620   for (auto &document_ptr : saved_animations->gifs_) {
621     int32 document_constructor_id = document_ptr->get_id();
622     if (document_constructor_id == telegram_api::documentEmpty::ID) {
623       LOG(ERROR) << "Empty saved animation document received";
624       continue;
625     }
626     CHECK(document_constructor_id == telegram_api::document::ID);
627     auto document =
628         td_->documents_manager_->on_get_document(move_tl_object_as<telegram_api::document>(document_ptr), DialogId());
629     if (document.type != Document::Type::Animation) {
630       LOG(ERROR) << "Receive " << document << " instead of animation as saved animation";
631       continue;
632     }
633     if (!is_repair) {
634       saved_animation_ids.push_back(document.file_id);
635     }
636   }
637 
638   if (is_repair) {
639     auto promises = std::move(repair_saved_animations_queries_);
640     repair_saved_animations_queries_.clear();
641     for (auto &promise : promises) {
642       promise.set_value(Unit());
643     }
644   } else {
645     on_load_saved_animations_finished(std::move(saved_animation_ids));
646 
647     LOG_IF(ERROR, get_saved_animations_hash("on_get_saved_animations") != saved_animations->hash_)
648         << "Saved animations hash mismatch: " << saved_animations->hash_ << " vs "
649         << get_saved_animations_hash("on_get_saved_animations 2");
650   }
651 }
652 
on_get_saved_animations_failed(bool is_repair,Status error)653 void AnimationsManager::on_get_saved_animations_failed(bool is_repair, Status error) {
654   CHECK(error.is_error());
655   if (!is_repair) {
656     are_saved_animations_being_loaded_ = false;
657     next_saved_animations_load_time_ = Time::now_cached() + Random::fast(5, 10);
658   }
659   auto &queries = is_repair ? repair_saved_animations_queries_ : load_saved_animations_queries_;
660   auto promises = std::move(queries);
661   queries.clear();
662   for (auto &promise : promises) {
663     promise.set_error(error.clone());
664   }
665 }
666 
get_saved_animations_hash(const char * source) const667 int64 AnimationsManager::get_saved_animations_hash(const char *source) const {
668   vector<uint64> numbers;
669   numbers.reserve(saved_animation_ids_.size());
670   for (auto animation_id : saved_animation_ids_) {
671     auto animation = get_animation(animation_id);
672     CHECK(animation != nullptr);
673     auto file_view = td_->file_manager_->get_file_view(animation_id);
674     CHECK(file_view.has_remote_location());
675     if (!file_view.remote_location().is_document()) {
676       LOG(ERROR) << "Saved animation remote location is not document: " << source << " " << file_view.remote_location();
677       continue;
678     }
679     numbers.push_back(file_view.remote_location().get_id());
680   }
681   return get_vector_hash(numbers);
682 }
683 
add_saved_animation(const tl_object_ptr<td_api::InputFile> & input_file,Promise<Unit> && promise)684 void AnimationsManager::add_saved_animation(const tl_object_ptr<td_api::InputFile> &input_file,
685                                             Promise<Unit> &&promise) {
686   if (!are_saved_animations_loaded_) {
687     load_saved_animations(std::move(promise));
688     return;
689   }
690 
691   auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Animation, input_file, DialogId(), false, false);
692   if (r_file_id.is_error()) {
693     return promise.set_error(Status::Error(400, r_file_id.error().message()));  // TODO do not drop error code
694   }
695 
696   add_saved_animation_impl(r_file_id.ok(), true, std::move(promise));
697 }
698 
send_save_gif_query(FileId animation_id,bool unsave,Promise<Unit> && promise)699 void AnimationsManager::send_save_gif_query(FileId animation_id, bool unsave, Promise<Unit> &&promise) {
700   TRY_STATUS_PROMISE(promise, G()->close_status());
701 
702   // TODO invokeAfter and log event
703   auto file_view = td_->file_manager_->get_file_view(animation_id);
704   CHECK(file_view.has_remote_location());
705   LOG_CHECK(file_view.remote_location().is_document()) << file_view.remote_location();
706   CHECK(!file_view.remote_location().is_web());
707   td_->create_handler<SaveGifQuery>(std::move(promise))
708       ->send(animation_id, file_view.remote_location().as_input_document(), unsave);
709 }
710 
add_saved_animation_by_id(FileId animation_id)711 void AnimationsManager::add_saved_animation_by_id(FileId animation_id) {
712   auto animation = get_animation(animation_id);
713   CHECK(animation != nullptr);
714   if (animation->has_stickers) {
715     return;
716   }
717 
718   // TODO log event
719   add_saved_animation_impl(animation_id, false, Auto());
720 }
721 
add_saved_animation_impl(FileId animation_id,bool add_on_server,Promise<Unit> && promise)722 void AnimationsManager::add_saved_animation_impl(FileId animation_id, bool add_on_server, Promise<Unit> &&promise) {
723   CHECK(!td_->auth_manager_->is_bot());
724 
725   auto file_view = td_->file_manager_->get_file_view(animation_id);
726   if (file_view.empty()) {
727     return promise.set_error(Status::Error(400, "Animation file not found"));
728   }
729 
730   LOG(INFO) << "Add saved animation " << animation_id << " with main file " << file_view.file_id();
731   if (!are_saved_animations_loaded_) {
732     load_saved_animations(
733         PromiseCreator::lambda([animation_id, add_on_server, promise = std::move(promise)](Result<> result) mutable {
734           if (result.is_ok()) {
735             send_closure(G()->animations_manager(), &AnimationsManager::add_saved_animation_impl, animation_id,
736                          add_on_server, std::move(promise));
737           } else {
738             promise.set_error(result.move_as_error());
739           }
740         }));
741     return;
742   }
743 
744   auto is_equal = [animation_id](FileId file_id) {
745     return file_id == animation_id ||
746            (file_id.get_remote() == animation_id.get_remote() && animation_id.get_remote() != 0);
747   };
748 
749   if (!saved_animation_ids_.empty() && is_equal(saved_animation_ids_[0])) {
750     // fast path
751     if (saved_animation_ids_[0].get_remote() == 0 && animation_id.get_remote() != 0) {
752       saved_animation_ids_[0] = animation_id;
753       save_saved_animations_to_database();
754     }
755 
756     return promise.set_value(Unit());
757   }
758 
759   auto animation = get_animation(animation_id);
760   if (animation == nullptr) {
761     return promise.set_error(Status::Error(400, "Animation not found"));
762   }
763   if (animation->mime_type != "video/mp4") {
764     return promise.set_error(Status::Error(400, "Only MPEG4 animations can be saved"));
765   }
766 
767   if (!file_view.has_remote_location()) {
768     return promise.set_error(Status::Error(400, "Can save only sent animations"));
769   }
770   if (file_view.remote_location().is_web()) {
771     return promise.set_error(Status::Error(400, "Can't save web animations"));
772   }
773   if (!file_view.remote_location().is_document()) {
774     return promise.set_error(Status::Error(400, "Can't save encrypted animations"));
775   }
776 
777   auto it = std::find_if(saved_animation_ids_.begin(), saved_animation_ids_.end(), is_equal);
778   if (it == saved_animation_ids_.end()) {
779     if (static_cast<int32>(saved_animation_ids_.size()) == saved_animations_limit_) {
780       saved_animation_ids_.back() = animation_id;
781     } else {
782       saved_animation_ids_.push_back(animation_id);
783     }
784     it = saved_animation_ids_.end() - 1;
785   }
786   std::rotate(saved_animation_ids_.begin(), it, it + 1);
787   CHECK(is_equal(saved_animation_ids_[0]));
788   if (saved_animation_ids_[0].get_remote() == 0 && animation_id.get_remote() != 0) {
789     saved_animation_ids_[0] = animation_id;
790   }
791 
792   send_update_saved_animations();
793   if (add_on_server) {
794     send_save_gif_query(animation_id, false, std::move(promise));
795   }
796 }
797 
remove_saved_animation(const tl_object_ptr<td_api::InputFile> & input_file,Promise<Unit> && promise)798 void AnimationsManager::remove_saved_animation(const tl_object_ptr<td_api::InputFile> &input_file,
799                                                Promise<Unit> &&promise) {
800   if (!are_saved_animations_loaded_) {
801     load_saved_animations(std::move(promise));
802     return;
803   }
804 
805   auto r_file_id = td_->file_manager_->get_input_file_id(FileType::Animation, input_file, DialogId(), false, false);
806   if (r_file_id.is_error()) {
807     return promise.set_error(Status::Error(400, r_file_id.error().message()));  // TODO do not drop error code
808   }
809 
810   FileId file_id = r_file_id.ok();
811   if (!td::remove(saved_animation_ids_, file_id)) {
812     return promise.set_value(Unit());
813   }
814 
815   auto animation = get_animation(file_id);
816   if (animation == nullptr) {
817     return promise.set_error(Status::Error(400, "Animation not found"));
818   }
819 
820   send_save_gif_query(file_id, true, std::move(promise));
821 
822   send_update_saved_animations();
823 }
824 
try_send_update_animation_search_parameters() const825 void AnimationsManager::try_send_update_animation_search_parameters() const {
826   auto update_animation_search_parameters = get_update_animation_search_parameters_object();
827   if (update_animation_search_parameters != nullptr) {
828     send_closure(G()->td(), &Td::send_update, std::move(update_animation_search_parameters));
829   }
830 }
831 
832 td_api::object_ptr<td_api::updateAnimationSearchParameters>
get_update_animation_search_parameters_object() const833 AnimationsManager::get_update_animation_search_parameters_object() const {
834   if (!is_animation_search_emojis_inited_ || !is_animation_search_provider_inited_) {
835     return nullptr;
836   }
837   return td_api::make_object<td_api::updateAnimationSearchParameters>(animation_search_provider_,
838                                                                       full_split(animation_search_emojis_, ','));
839 }
840 
get_update_saved_animations_object() const841 td_api::object_ptr<td_api::updateSavedAnimations> AnimationsManager::get_update_saved_animations_object() const {
842   return td_api::make_object<td_api::updateSavedAnimations>(
843       td_->file_manager_->get_file_ids_object(saved_animation_ids_));
844 }
845 
send_update_saved_animations(bool from_database)846 void AnimationsManager::send_update_saved_animations(bool from_database) {
847   if (are_saved_animations_loaded_) {
848     vector<FileId> new_saved_animation_file_ids = saved_animation_ids_;
849     for (auto &animation_id : saved_animation_ids_) {
850       auto animation = get_animation(animation_id);
851       CHECK(animation != nullptr);
852       if (animation->thumbnail.file_id.is_valid()) {
853         new_saved_animation_file_ids.push_back(animation->thumbnail.file_id);
854       }
855       if (animation->animated_thumbnail.file_id.is_valid()) {
856         new_saved_animation_file_ids.push_back(animation->animated_thumbnail.file_id);
857       }
858     }
859     std::sort(new_saved_animation_file_ids.begin(), new_saved_animation_file_ids.end());
860     if (new_saved_animation_file_ids != saved_animation_file_ids_) {
861       td_->file_manager_->change_files_source(get_saved_animations_file_source_id(), saved_animation_file_ids_,
862                                               new_saved_animation_file_ids);
863       saved_animation_file_ids_ = std::move(new_saved_animation_file_ids);
864     }
865 
866     send_closure(G()->td(), &Td::send_update, get_update_saved_animations_object());
867 
868     if (!from_database) {
869       save_saved_animations_to_database();
870     }
871   }
872 }
873 
save_saved_animations_to_database()874 void AnimationsManager::save_saved_animations_to_database() {
875   if (G()->parameters().use_file_db) {
876     LOG(INFO) << "Save saved animations to database";
877     AnimationListLogEvent log_event(saved_animation_ids_);
878     G()->td_db()->get_sqlite_pmc()->set("ans", log_event_store(log_event).as_slice().str(), Auto());
879   }
880 }
881 
get_saved_animations_file_source_id()882 FileSourceId AnimationsManager::get_saved_animations_file_source_id() {
883   if (!saved_animations_file_source_id_.is_valid()) {
884     saved_animations_file_source_id_ = td_->file_reference_manager_->create_saved_animations_file_source();
885   }
886   return saved_animations_file_source_id_;
887 }
888 
get_animation_search_text(FileId file_id) const889 string AnimationsManager::get_animation_search_text(FileId file_id) const {
890   auto animation = get_animation(file_id);
891   CHECK(animation != nullptr);
892   return animation->file_name;
893 }
894 
after_get_difference()895 void AnimationsManager::after_get_difference() {
896   if (td_->is_online() && !td_->auth_manager_->is_bot()) {
897     get_saved_animations(Auto());
898   }
899 }
900 
get_current_state(vector<td_api::object_ptr<td_api::Update>> & updates) const901 void AnimationsManager::get_current_state(vector<td_api::object_ptr<td_api::Update>> &updates) const {
902   if (td_->auth_manager_->is_bot()) {
903     return;
904   }
905 
906   if (are_saved_animations_loaded_) {
907     updates.push_back(get_update_saved_animations_object());
908   }
909   auto update_animation_search_parameters = get_update_animation_search_parameters_object();
910   if (update_animation_search_parameters != nullptr) {
911     updates.push_back(std::move(update_animation_search_parameters));
912   }
913 }
914 
915 }  // namespace td
916