data_poll.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "data/data_poll.h"
  8. #include "api/api_text_entities.h"
  9. #include "data/data_user.h"
  10. #include "data/data_session.h"
  11. #include "base/call_delayed.h"
  12. #include "main/main_session.h"
  13. #include "api/api_text_entities.h"
  14. #include "ui/text/text_options.h"
  15. namespace {
  16. constexpr auto kShortPollTimeout = 30 * crl::time(1000);
  17. constexpr auto kReloadAfterAutoCloseDelay = crl::time(1000);
  18. const PollAnswer *AnswerByOption(
  19. const std::vector<PollAnswer> &list,
  20. const QByteArray &option) {
  21. const auto i = ranges::find(
  22. list,
  23. option,
  24. [](const PollAnswer &a) { return a.option; });
  25. return (i != end(list)) ? &*i : nullptr;
  26. }
  27. PollAnswer *AnswerByOption(
  28. std::vector<PollAnswer> &list,
  29. const QByteArray &option) {
  30. return const_cast<PollAnswer*>(AnswerByOption(
  31. std::as_const(list),
  32. option));
  33. }
  34. } // namespace
  35. PollData::PollData(not_null<Data::Session*> owner, PollId id)
  36. : id(id)
  37. , _owner(owner) {
  38. }
  39. Data::Session &PollData::owner() const {
  40. return *_owner;
  41. }
  42. Main::Session &PollData::session() const {
  43. return _owner->session();
  44. }
  45. bool PollData::closeByTimer() {
  46. if (closed()) {
  47. return false;
  48. }
  49. _flags |= Flag::Closed;
  50. ++version;
  51. base::call_delayed(kReloadAfterAutoCloseDelay, &_owner->session(), [=] {
  52. _lastResultsUpdate = -1; // Force reload results.
  53. ++version;
  54. _owner->notifyPollUpdateDelayed(this);
  55. });
  56. return true;
  57. }
  58. bool PollData::applyChanges(const MTPDpoll &poll) {
  59. Expects(poll.vid().v == id);
  60. const auto newQuestion = TextWithEntities{
  61. .text = qs(poll.vquestion().data().vtext()),
  62. .entities = Api::EntitiesFromMTP(
  63. &session(),
  64. poll.vquestion().data().ventities().v),
  65. };
  66. const auto newFlags = (poll.is_closed() ? Flag::Closed : Flag(0))
  67. | (poll.is_public_voters() ? Flag::PublicVotes : Flag(0))
  68. | (poll.is_multiple_choice() ? Flag::MultiChoice : Flag(0))
  69. | (poll.is_quiz() ? Flag::Quiz : Flag(0));
  70. const auto newCloseDate = poll.vclose_date().value_or_empty();
  71. const auto newClosePeriod = poll.vclose_period().value_or_empty();
  72. auto newAnswers = ranges::views::all(
  73. poll.vanswers().v
  74. ) | ranges::views::transform([&](const MTPPollAnswer &data) {
  75. return data.match([&](const MTPDpollAnswer &answer) {
  76. auto result = PollAnswer();
  77. result.option = answer.voption().v;
  78. result.text = TextWithEntities{
  79. .text = qs(answer.vtext().data().vtext()),
  80. .entities = Api::EntitiesFromMTP(
  81. &session(),
  82. answer.vtext().data().ventities().v),
  83. };
  84. return result;
  85. });
  86. }) | ranges::views::take(
  87. kMaxOptions
  88. ) | ranges::to_vector;
  89. const auto changed1 = (question != newQuestion)
  90. || (closeDate != newCloseDate)
  91. || (closePeriod != newClosePeriod)
  92. || (_flags != newFlags);
  93. const auto changed2 = (answers != newAnswers);
  94. if (!changed1 && !changed2) {
  95. return false;
  96. }
  97. if (changed1) {
  98. question = newQuestion;
  99. closeDate = newCloseDate;
  100. closePeriod = newClosePeriod;
  101. _flags = newFlags;
  102. }
  103. if (changed2) {
  104. std::swap(answers, newAnswers);
  105. for (const auto &old : newAnswers) {
  106. if (const auto current = answerByOption(old.option)) {
  107. current->votes = old.votes;
  108. current->chosen = old.chosen;
  109. current->correct = old.correct;
  110. }
  111. }
  112. }
  113. ++version;
  114. return true;
  115. }
  116. bool PollData::applyResults(const MTPPollResults &results) {
  117. return results.match([&](const MTPDpollResults &results) {
  118. _lastResultsUpdate = crl::now();
  119. const auto newTotalVoters
  120. = results.vtotal_voters().value_or(totalVoters);
  121. auto changed = (newTotalVoters != totalVoters);
  122. if (const auto list = results.vresults()) {
  123. for (const auto &result : list->v) {
  124. if (applyResultToAnswers(result, results.is_min())) {
  125. changed = true;
  126. }
  127. }
  128. }
  129. if (const auto recent = results.vrecent_voters()) {
  130. const auto recentChanged = !ranges::equal(
  131. recentVoters,
  132. recent->v,
  133. ranges::equal_to(),
  134. &PeerData::id,
  135. peerFromMTP);
  136. if (recentChanged) {
  137. changed = true;
  138. recentVoters = ranges::views::all(
  139. recent->v
  140. ) | ranges::views::transform([&](MTPPeer peerId) {
  141. const auto peer = _owner->peer(peerFromMTP(peerId));
  142. return peer->isMinimalLoaded() ? peer.get() : nullptr;
  143. }) | ranges::views::filter([](PeerData *peer) {
  144. return peer != nullptr;
  145. }) | ranges::views::transform([](PeerData *peer) {
  146. return not_null(peer);
  147. }) | ranges::to_vector;
  148. }
  149. }
  150. if (results.vsolution()) {
  151. auto newSolution = TextWithEntities{
  152. results.vsolution().value_or_empty(),
  153. Api::EntitiesFromMTP(
  154. &_owner->session(),
  155. results.vsolution_entities().value_or_empty())
  156. };
  157. if (solution != newSolution) {
  158. solution = std::move(newSolution);
  159. changed = true;
  160. }
  161. }
  162. if (!changed) {
  163. return false;
  164. }
  165. totalVoters = newTotalVoters;
  166. ++version;
  167. return changed;
  168. });
  169. }
  170. bool PollData::checkResultsReload(crl::time now) {
  171. if (_lastResultsUpdate > 0
  172. && _lastResultsUpdate + kShortPollTimeout > now) {
  173. return false;
  174. } else if (closed() && _lastResultsUpdate >= 0) {
  175. return false;
  176. }
  177. _lastResultsUpdate = now;
  178. return true;
  179. }
  180. PollAnswer *PollData::answerByOption(const QByteArray &option) {
  181. return AnswerByOption(answers, option);
  182. }
  183. const PollAnswer *PollData::answerByOption(const QByteArray &option) const {
  184. return AnswerByOption(answers, option);
  185. }
  186. bool PollData::applyResultToAnswers(
  187. const MTPPollAnswerVoters &result,
  188. bool isMinResults) {
  189. return result.match([&](const MTPDpollAnswerVoters &voters) {
  190. const auto &option = voters.voption().v;
  191. const auto answer = answerByOption(option);
  192. if (!answer) {
  193. return false;
  194. }
  195. auto changed = (answer->votes != voters.vvoters().v);
  196. if (changed) {
  197. answer->votes = voters.vvoters().v;
  198. }
  199. if (!isMinResults) {
  200. if (answer->chosen != voters.is_chosen()) {
  201. answer->chosen = voters.is_chosen();
  202. changed = true;
  203. }
  204. }
  205. if (voters.is_correct() && !answer->correct) {
  206. answer->correct = voters.is_correct();
  207. changed = true;
  208. }
  209. return changed;
  210. });
  211. }
  212. void PollData::setFlags(Flags flags) {
  213. if (_flags != flags) {
  214. _flags = flags;
  215. ++version;
  216. }
  217. }
  218. PollData::Flags PollData::flags() const {
  219. return _flags;
  220. }
  221. bool PollData::voted() const {
  222. return ranges::contains(answers, true, &PollAnswer::chosen);
  223. }
  224. bool PollData::closed() const {
  225. return (_flags & Flag::Closed);
  226. }
  227. bool PollData::publicVotes() const {
  228. return (_flags & Flag::PublicVotes);
  229. }
  230. bool PollData::multiChoice() const {
  231. return (_flags & Flag::MultiChoice);
  232. }
  233. bool PollData::quiz() const {
  234. return (_flags & Flag::Quiz);
  235. }
  236. MTPPoll PollDataToMTP(not_null<const PollData*> poll, bool close) {
  237. const auto convert = [&](const PollAnswer &answer) {
  238. return MTP_pollAnswer(
  239. MTP_textWithEntities(
  240. MTP_string(answer.text.text),
  241. Api::EntitiesToMTP(&poll->session(), answer.text.entities)),
  242. MTP_bytes(answer.option));
  243. };
  244. auto answers = QVector<MTPPollAnswer>();
  245. answers.reserve(poll->answers.size());
  246. ranges::transform(
  247. poll->answers,
  248. ranges::back_inserter(answers),
  249. convert);
  250. using Flag = MTPDpoll::Flag;
  251. const auto flags = ((poll->closed() || close) ? Flag::f_closed : Flag(0))
  252. | (poll->multiChoice() ? Flag::f_multiple_choice : Flag(0))
  253. | (poll->publicVotes() ? Flag::f_public_voters : Flag(0))
  254. | (poll->quiz() ? Flag::f_quiz : Flag(0))
  255. | (poll->closePeriod > 0 ? Flag::f_close_period : Flag(0))
  256. | (poll->closeDate > 0 ? Flag::f_close_date : Flag(0));
  257. return MTP_poll(
  258. MTP_long(poll->id),
  259. MTP_flags(flags),
  260. MTP_textWithEntities(
  261. MTP_string(poll->question.text),
  262. Api::EntitiesToMTP(&poll->session(), poll->question.entities)),
  263. MTP_vector<MTPPollAnswer>(answers),
  264. MTP_int(poll->closePeriod),
  265. MTP_int(poll->closeDate));
  266. }
  267. MTPInputMedia PollDataToInputMedia(
  268. not_null<const PollData*> poll,
  269. bool close) {
  270. auto inputFlags = MTPDinputMediaPoll::Flag(0)
  271. | (poll->quiz()
  272. ? MTPDinputMediaPoll::Flag::f_correct_answers
  273. : MTPDinputMediaPoll::Flag(0));
  274. auto correct = QVector<MTPbytes>();
  275. for (const auto &answer : poll->answers) {
  276. if (answer.correct) {
  277. correct.push_back(MTP_bytes(answer.option));
  278. }
  279. }
  280. auto solution = poll->solution;
  281. const auto prepareFlags = Ui::ItemTextDefaultOptions().flags;
  282. TextUtilities::PrepareForSending(solution, prepareFlags);
  283. TextUtilities::Trim(solution);
  284. const auto sentEntities = Api::EntitiesToMTP(
  285. &poll->session(),
  286. solution.entities,
  287. Api::ConvertOption::SkipLocal);
  288. if (!solution.text.isEmpty()) {
  289. inputFlags |= MTPDinputMediaPoll::Flag::f_solution;
  290. }
  291. if (!sentEntities.v.isEmpty()) {
  292. inputFlags |= MTPDinputMediaPoll::Flag::f_solution_entities;
  293. }
  294. return MTP_inputMediaPoll(
  295. MTP_flags(inputFlags),
  296. PollDataToMTP(poll, close),
  297. MTP_vector<MTPbytes>(correct),
  298. MTP_string(solution.text),
  299. sentEntities);
  300. }