aboutsummaryrefslogtreecommitdiffhomepage
path: root/td/telegram/HashtagHints.cpp
blob: 18fd50315a21500f6ca8e9debba6d7f8953e76b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2026
//
// 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/HashtagHints.h"

#include "td/telegram/Global.h"
#include "td/telegram/TdDb.h"

#include "td/db/SqliteKeyValueAsync.h"

#include "td/utils/HashTableUtils.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/utf8.h"

namespace td {

HashtagHints::HashtagHints(string mode, char first_character, ActorShared<> parent)
    : mode_(std::move(mode)), first_character_(first_character), parent_(std::move(parent)) {
}

void HashtagHints::start_up() {
  if (G()->use_sqlite_pmc()) {  // TODO hashtag hints should not depend on use_sqlite_pmc
    G()->td_db()->get_sqlite_pmc()->get(get_key(),
                                        PromiseCreator::lambda([actor_id = actor_id(this)](Result<string> res) {
                                          send_closure(actor_id, &HashtagHints::from_db, std::move(res), false);
                                        }));
  }
}

void HashtagHints::hashtag_used(const string &hashtag) {
  if (!sync_with_db_) {
    return;
  }
  hashtag_used_impl(hashtag);
  G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(keys_to_strings(hints_.search_empty(101).second)),
                                      Promise<Unit>());
}

void HashtagHints::remove_hashtag(string hashtag, Promise<Unit> promise) {
  if (!sync_with_db_) {
    return promise.set_value(Unit());
  }
  if (hashtag[0] == first_character_) {
    hashtag = hashtag.substr(1);
  }
  auto key = Hash<string>()(hashtag);
  if (hints_.has_key(key)) {
    hints_.remove(key);
    G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(keys_to_strings(hints_.search_empty(101).second)),
                                        Promise<Unit>());
    promise.set_value(Unit());  // set promise explicitly, because sqlite_pmc waits for too long before setting promise
  } else {
    promise.set_value(Unit());
  }
}

void HashtagHints::clear(Promise<Unit> promise) {
  if (!sync_with_db_) {
    return promise.set_value(Unit());
  }
  hints_ = {};
  G()->td_db()->get_sqlite_pmc()->set(get_key(), serialize(vector<string>()), Promise<Unit>());
  promise.set_value(Unit());
}

void HashtagHints::query(const string &prefix, int32 limit, Promise<vector<string>> promise) {
  if (!sync_with_db_) {
    promise.set_value(vector<string>());
    return;
  }

  auto query = Slice(prefix).substr(prefix[0] == first_character_ ? 1 : 0);
  auto result = query.empty() ? hints_.search_empty(limit) : hints_.search(query, limit);
  promise.set_value(keys_to_strings(result.second));
}

string HashtagHints::get_key() const {
  return "hashtag_hints#" + mode_;
}

void HashtagHints::hashtag_used_impl(const string &hashtag) {
  if (!check_utf8(hashtag)) {
    LOG(ERROR) << "Trying to add invalid UTF-8 hashtag \"" << hashtag << '"';
    return;
  }

  auto key = Hash<string>()(hashtag);
  hints_.add(key, hashtag);
  hints_.set_rating(key, -++counter_);
}

void HashtagHints::from_db(Result<string> data, bool dummy) {
  if (G()->close_flag()) {
    return;
  }

  sync_with_db_ = true;
  if (data.is_error() || data.ok().empty()) {
    return;
  }
  vector<string> hashtags;
  auto status = unserialize(hashtags, data.ok());
  if (status.is_error()) {
    LOG(ERROR) << "Failed to unserialize hashtag hints: " << status;
    return;
  }

  for (auto it = hashtags.rbegin(); it != hashtags.rend(); ++it) {
    hashtag_used_impl(*it);
  }
}

vector<string> HashtagHints::keys_to_strings(const vector<int64> &keys) {
  vector<string> result;
  result.reserve(keys.size());
  for (auto &it : keys) {
    result.push_back(hints_.key_to_string(it));
  }
  return result;
}

}  // namespace td