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/files/FileStats.h"
8 
9 #include "td/telegram/files/FileLoaderUtils.h"
10 #include "td/telegram/td_api.h"
11 
12 #include "td/utils/algorithm.h"
13 #include "td/utils/common.h"
14 #include "td/utils/format.h"
15 #include "td/utils/misc.h"
16 
17 #include <algorithm>
18 #include <unordered_set>
19 #include <utility>
20 
21 namespace td {
22 
get_storage_statistics_fast_object() const23 tl_object_ptr<td_api::storageStatisticsFast> FileStatsFast::get_storage_statistics_fast_object() const {
24   return make_tl_object<td_api::storageStatisticsFast>(size, count, database_size, language_pack_database_size,
25                                                        log_size);
26 }
27 
add(StatByType & by_type,FileType file_type,int64 size)28 void FileStats::add(StatByType &by_type, FileType file_type, int64 size) {
29   auto pos = static_cast<size_t>(file_type);
30   CHECK(pos < stat_by_type_.size());
31   by_type[pos].size += size;
32   by_type[pos].cnt++;
33 }
34 
add_impl(const FullFileInfo & info)35 void FileStats::add_impl(const FullFileInfo &info) {
36   if (split_by_owner_dialog_id_) {
37     add(stat_by_owner_dialog_id_[info.owner_dialog_id], info.file_type, info.size);
38   } else {
39     add(stat_by_type_, info.file_type, info.size);
40   }
41 }
42 
add_copy(const FullFileInfo & info)43 void FileStats::add_copy(const FullFileInfo &info) {
44   add_impl(info);
45   if (need_all_files_) {
46     all_files_.push_back(info);
47   }
48 }
49 
add(FullFileInfo && info)50 void FileStats::add(FullFileInfo &&info) {
51   add_impl(info);
52   if (need_all_files_) {
53     all_files_.push_back(std::move(info));
54   }
55 }
56 
get_nontemp_stat(const FileStats::StatByType & by_type)57 FileTypeStat FileStats::get_nontemp_stat(const FileStats::StatByType &by_type) {
58   FileTypeStat stat;
59   for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
60     if (FileType(i) != FileType::Temp) {
61       stat.size += by_type[i].size;
62       stat.cnt += by_type[i].cnt;
63     }
64   }
65   return stat;
66 }
67 
get_total_nontemp_stat() const68 FileTypeStat FileStats::get_total_nontemp_stat() const {
69   if (!split_by_owner_dialog_id_) {
70     return get_nontemp_stat(stat_by_type_);
71   }
72   FileTypeStat stat;
73   for (auto &dialog : stat_by_owner_dialog_id_) {
74     auto tmp = get_nontemp_stat(dialog.second);
75     stat.size += tmp.size;
76     stat.cnt += tmp.cnt;
77   }
78   return stat;
79 }
80 
apply_dialog_limit(int32 limit)81 void FileStats::apply_dialog_limit(int32 limit) {
82   if (limit == -1) {
83     return;
84   }
85   if (!split_by_owner_dialog_id_) {
86     return;
87   }
88 
89   std::vector<std::pair<int64, DialogId>> dialogs;
90   for (auto &dialog : stat_by_owner_dialog_id_) {
91     if (!dialog.first.is_valid()) {
92       continue;
93     }
94     int64 size = 0;
95     for (auto &it : dialog.second) {
96       size += it.size;
97     }
98     dialogs.emplace_back(size, dialog.first);
99   }
100   size_t prefix = dialogs.size();
101   if (prefix > static_cast<size_t>(limit)) {
102     prefix = static_cast<size_t>(limit);
103   }
104   std::partial_sort(dialogs.begin(), dialogs.begin() + prefix, dialogs.end(),
105                     [](const auto &x, const auto &y) { return x.first > y.first; });
106   dialogs.resize(prefix);
107 
108   apply_dialog_ids(transform(dialogs, [](const auto &dialog) { return dialog.second; }));
109 }
110 
apply_dialog_ids(const vector<DialogId> & dialog_ids)111 void FileStats::apply_dialog_ids(const vector<DialogId> &dialog_ids) {
112   std::unordered_set<DialogId, DialogIdHash> all_dialogs(dialog_ids.begin(), dialog_ids.end());
113   StatByType other_stats;
114   bool other_flag = false;
115   for (auto it = stat_by_owner_dialog_id_.begin(); it != stat_by_owner_dialog_id_.end();) {
116     if (all_dialogs.count(it->first)) {
117       ++it;
118     } else {
119       for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
120         other_stats[i].size += it->second[i].size;
121         other_stats[i].cnt += it->second[i].cnt;
122       }
123       other_flag = true;
124       it = stat_by_owner_dialog_id_.erase(it);
125     }
126   }
127 
128   if (other_flag) {
129     DialogId other_dialog_id;  // prevents MSVC warning C4709: comma operator within array index expression
130     stat_by_owner_dialog_id_[other_dialog_id] = other_stats;
131   }
132 }
133 
get_storage_statistics_by_chat_object(DialogId dialog_id,const FileStats::StatByType & stat_by_type_)134 td_api::object_ptr<td_api::storageStatisticsByChat> FileStats::get_storage_statistics_by_chat_object(
135     DialogId dialog_id, const FileStats::StatByType &stat_by_type_) {
136   auto stats = make_tl_object<td_api::storageStatisticsByChat>(dialog_id.get(), 0, 0, Auto());
137   FileStats::StatByType aggregated_stats;
138   for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
139     auto file_type = narrow_cast<size_t>(get_main_file_type(static_cast<FileType>(i)));
140     aggregated_stats[file_type].size += stat_by_type_[i].size;
141     aggregated_stats[file_type].cnt += stat_by_type_[i].cnt;
142   }
143 
144   for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
145     auto size = aggregated_stats[i].size;
146     auto cnt = aggregated_stats[i].cnt;
147 
148     if (size == 0) {
149       continue;
150     }
151 
152     auto file_type = static_cast<FileType>(i);
153     stats->size_ += size;
154     stats->count_ += cnt;
155     stats->by_file_type_.push_back(
156         make_tl_object<td_api::storageStatisticsByFileType>(get_file_type_object(file_type), size, cnt));
157   }
158   return stats;
159 }
160 
get_storage_statistics_object() const161 tl_object_ptr<td_api::storageStatistics> FileStats::get_storage_statistics_object() const {
162   auto stats = make_tl_object<td_api::storageStatistics>(0, 0, Auto());
163   if (!split_by_owner_dialog_id_) {
164     stats->by_chat_.reserve(1);
165     stats->by_chat_.push_back(get_storage_statistics_by_chat_object(DialogId(), stat_by_type_));
166   } else {
167     stats->by_chat_.reserve(stat_by_owner_dialog_id_.size());
168     for (auto &by_dialog : stat_by_owner_dialog_id_) {
169       stats->by_chat_.push_back(get_storage_statistics_by_chat_object(by_dialog.first, by_dialog.second));
170     }
171     std::sort(stats->by_chat_.begin(), stats->by_chat_.end(), [](const auto &x, const auto &y) {
172       if (x->chat_id_ == 0 || y->chat_id_ == 0) {
173         return (x->chat_id_ == 0) < (y->chat_id_ == 0);
174       }
175       return x->size_ > y->size_;
176     });
177   }
178   for (const auto &by_dialog : stats->by_chat_) {
179     stats->size_ += by_dialog->size_;
180     stats->count_ += by_dialog->count_;
181   }
182   return stats;
183 }
184 
get_dialog_ids() const185 vector<DialogId> FileStats::get_dialog_ids() const {
186   vector<DialogId> res;
187   if (!split_by_owner_dialog_id_) {
188     return res;
189   }
190   res.reserve(stat_by_owner_dialog_id_.size());
191   for (auto &by_dialog : stat_by_owner_dialog_id_) {
192     if (by_dialog.first.is_valid()) {
193       res.push_back(by_dialog.first);
194     }
195   }
196   return res;
197 }
198 
get_all_files()199 vector<FullFileInfo> FileStats::get_all_files() {
200   return std::move(all_files_);
201 }
202 
operator <<(StringBuilder & sb,const FileTypeStat & stat)203 static StringBuilder &operator<<(StringBuilder &sb, const FileTypeStat &stat) {
204   return sb << tag("size", format::as_size(stat.size)) << tag("count", stat.cnt);
205 }
206 
operator <<(StringBuilder & sb,const FileStats & file_stats)207 StringBuilder &operator<<(StringBuilder &sb, const FileStats &file_stats) {
208   if (!file_stats.split_by_owner_dialog_id_) {
209     FileTypeStat total_stat;
210     for (auto &type_stat : file_stats.stat_by_type_) {
211       total_stat.size += type_stat.size;
212       total_stat.cnt += type_stat.cnt;
213     }
214 
215     sb << "[FileStat " << tag("total", total_stat);
216     for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
217       sb << tag(get_file_type_name(FileType(i)), file_stats.stat_by_type_[i]);
218     }
219     sb << "]";
220   } else {
221     {
222       FileTypeStat total_stat;
223       for (auto &by_type : file_stats.stat_by_owner_dialog_id_) {
224         for (auto &type_stat : by_type.second) {
225           total_stat.size += type_stat.size;
226           total_stat.cnt += type_stat.cnt;
227         }
228       }
229       sb << "[FileStat " << tag("total", total_stat);
230     }
231     for (auto &by_type : file_stats.stat_by_owner_dialog_id_) {
232       FileTypeStat dialog_stat;
233       for (auto &type_stat : by_type.second) {
234         dialog_stat.size += type_stat.size;
235         dialog_stat.cnt += type_stat.cnt;
236       }
237 
238       sb << "[FileStat " << tag("owner_dialog_id", by_type.first) << tag("total", dialog_stat);
239       for (int32 i = 0; i < MAX_FILE_TYPE; i++) {
240         sb << tag(get_file_type_name(FileType(i)), by_type.second[i]);
241       }
242       sb << "]";
243     }
244     sb << "]";
245   }
246 
247   return sb;
248 }
249 
250 }  // namespace td
251