1 // Copyright (C) 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "icing/store/usage-store.h"
16
17 #include "icing/file/file-backed-vector.h"
18 #include "icing/proto/usage.pb.h"
19 #include "icing/store/document-id.h"
20 #include "icing/util/crc32.h"
21
22 namespace icing {
23 namespace lib {
24
25 namespace {
MakeUsageScoreCacheFilename(const std::string & base_dir)26 std::string MakeUsageScoreCacheFilename(const std::string& base_dir) {
27 return absl_ports::StrCat(base_dir, "/usage-scores");
28 }
29 } // namespace
30
Create(const Filesystem * filesystem,const std::string & base_dir)31 libtextclassifier3::StatusOr<std::unique_ptr<UsageStore>> UsageStore::Create(
32 const Filesystem* filesystem, const std::string& base_dir) {
33 ICING_RETURN_ERROR_IF_NULL(filesystem);
34
35 if (!filesystem->CreateDirectoryRecursively(base_dir.c_str())) {
36 return absl_ports::InternalError(absl_ports::StrCat(
37 "Failed to create UsageStore directory: ", base_dir));
38 }
39
40 const std::string score_cache_filename =
41 MakeUsageScoreCacheFilename(base_dir);
42
43 auto usage_score_cache_or = FileBackedVector<UsageScores>::Create(
44 *filesystem, score_cache_filename,
45 MemoryMappedFile::READ_WRITE_AUTO_SYNC);
46
47 if (absl_ports::IsFailedPrecondition(usage_score_cache_or.status())) {
48 // File checksum doesn't match the stored checksum. Delete and recreate the
49 // file.
50 ICING_RETURN_IF_ERROR(
51 FileBackedVector<int64_t>::Delete(*filesystem, score_cache_filename));
52
53 ICING_VLOG(1) << "The score cache file in UsageStore is corrupted, all "
54 "scores have been reset.";
55
56 usage_score_cache_or = FileBackedVector<UsageScores>::Create(
57 *filesystem, score_cache_filename,
58 MemoryMappedFile::READ_WRITE_AUTO_SYNC);
59 }
60
61 if (!usage_score_cache_or.ok()) {
62 ICING_LOG(ERROR) << usage_score_cache_or.status().error_message()
63 << "Failed to initialize usage_score_cache";
64 return usage_score_cache_or.status();
65 }
66
67 return std::unique_ptr<UsageStore>(new UsageStore(
68 std::move(usage_score_cache_or).ValueOrDie(), *filesystem, base_dir));
69 }
70
AddUsageReport(const UsageReport & report,DocumentId document_id)71 libtextclassifier3::Status UsageStore::AddUsageReport(const UsageReport& report,
72 DocumentId document_id) {
73 if (!IsDocumentIdValid(document_id)) {
74 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
75 "Document id %d is invalid.", document_id));
76 }
77
78 // We don't need a copy here because we'll set the value at the same index.
79 // This won't unintentionally grow the underlying file since we already have
80 // enough space for the current index.
81 auto usage_scores_or = usage_score_cache_->Get(document_id);
82
83 // OutOfRange means that the mapper hasn't seen this document id before, it's
84 // not an error here.
85 UsageScores usage_scores;
86 if (usage_scores_or.ok()) {
87 usage_scores = *std::move(usage_scores_or).ValueOrDie();
88 } else if (!absl_ports::IsOutOfRange(usage_scores_or.status())) {
89 // Real error
90 return usage_scores_or.status();
91 }
92
93 // Update last used timestamps and type counts. The counts won't be
94 // incremented if they are already the maximum values. The timestamp from
95 // UsageReport is in milliseconds, we need to convert it to seconds.
96 int64_t report_timestamp_s = report.usage_timestamp_ms() / 1000;
97
98 switch (report.usage_type()) {
99 case UsageReport::USAGE_TYPE1:
100 if (report_timestamp_s > std::numeric_limits<uint32_t>::max()) {
101 usage_scores.usage_type1_last_used_timestamp_s =
102 std::numeric_limits<uint32_t>::max();
103 } else if (report_timestamp_s >
104 usage_scores.usage_type1_last_used_timestamp_s) {
105 usage_scores.usage_type1_last_used_timestamp_s = report_timestamp_s;
106 }
107
108 if (usage_scores.usage_type1_count < std::numeric_limits<int>::max()) {
109 ++usage_scores.usage_type1_count;
110 }
111 break;
112 case UsageReport::USAGE_TYPE2:
113 if (report_timestamp_s > std::numeric_limits<uint32_t>::max()) {
114 usage_scores.usage_type2_last_used_timestamp_s =
115 std::numeric_limits<uint32_t>::max();
116 } else if (report_timestamp_s >
117 usage_scores.usage_type2_last_used_timestamp_s) {
118 usage_scores.usage_type2_last_used_timestamp_s = report_timestamp_s;
119 }
120
121 if (usage_scores.usage_type2_count < std::numeric_limits<int>::max()) {
122 ++usage_scores.usage_type2_count;
123 }
124 break;
125 case UsageReport::USAGE_TYPE3:
126 if (report_timestamp_s > std::numeric_limits<uint32_t>::max()) {
127 usage_scores.usage_type3_last_used_timestamp_s =
128 std::numeric_limits<uint32_t>::max();
129 } else if (report_timestamp_s >
130 usage_scores.usage_type3_last_used_timestamp_s) {
131 usage_scores.usage_type3_last_used_timestamp_s = report_timestamp_s;
132 }
133
134 if (usage_scores.usage_type3_count < std::numeric_limits<int>::max()) {
135 ++usage_scores.usage_type3_count;
136 }
137 }
138
139 // Write updated usage scores to file.
140 return usage_score_cache_->Set(document_id, usage_scores);
141 }
142
DeleteUsageScores(DocumentId document_id)143 libtextclassifier3::Status UsageStore::DeleteUsageScores(
144 DocumentId document_id) {
145 if (!IsDocumentIdValid(document_id)) {
146 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
147 "Document id %d is invalid.", document_id));
148 }
149
150 if (document_id >= usage_score_cache_->num_elements()) {
151 // Nothing to delete.
152 return libtextclassifier3::Status::OK;
153 }
154
155 // Clear all the scores of the document.
156 return usage_score_cache_->Set(document_id, UsageScores());
157 }
158
159 libtextclassifier3::StatusOr<UsageStore::UsageScores>
GetUsageScores(DocumentId document_id)160 UsageStore::GetUsageScores(DocumentId document_id) {
161 if (!IsDocumentIdValid(document_id)) {
162 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
163 "Document id %d is invalid.", document_id));
164 }
165
166 auto usage_scores_or = usage_score_cache_->GetCopy(document_id);
167 if (absl_ports::IsOutOfRange(usage_scores_or.status())) {
168 // No usage scores found. Return the default scores.
169 return UsageScores();
170 } else if (!usage_scores_or.ok()) {
171 // Pass up any other errors.
172 return usage_scores_or.status();
173 }
174
175 return std::move(usage_scores_or).ValueOrDie();
176 }
177
SetUsageScores(DocumentId document_id,const UsageScores & usage_scores)178 libtextclassifier3::Status UsageStore::SetUsageScores(
179 DocumentId document_id, const UsageScores& usage_scores) {
180 if (!IsDocumentIdValid(document_id)) {
181 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
182 "Document id %d is invalid.", document_id));
183 }
184
185 return usage_score_cache_->Set(document_id, usage_scores);
186 }
187
CloneUsageScores(DocumentId from_document_id,DocumentId to_document_id)188 libtextclassifier3::Status UsageStore::CloneUsageScores(
189 DocumentId from_document_id, DocumentId to_document_id) {
190 if (!IsDocumentIdValid(from_document_id)) {
191 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
192 "from_document_id %d is invalid.", from_document_id));
193 }
194
195 if (!IsDocumentIdValid(to_document_id)) {
196 return absl_ports::InvalidArgumentError(IcingStringUtil::StringPrintf(
197 "to_document_id %d is invalid.", to_document_id));
198 }
199
200 auto usage_scores_or = usage_score_cache_->GetCopy(from_document_id);
201 if (usage_scores_or.ok()) {
202 return usage_score_cache_->Set(to_document_id,
203 std::move(usage_scores_or).ValueOrDie());
204 } else if (absl_ports::IsOutOfRange(usage_scores_or.status())) {
205 // No usage scores found. Set default scores to to_document_id.
206 return usage_score_cache_->Set(to_document_id, UsageScores());
207 }
208
209 // Real error
210 return usage_scores_or.status();
211 }
212
PersistToDisk()213 libtextclassifier3::Status UsageStore::PersistToDisk() {
214 return usage_score_cache_->PersistToDisk();
215 }
216
UpdateChecksum()217 libtextclassifier3::StatusOr<Crc32> UsageStore::UpdateChecksum() {
218 return usage_score_cache_->UpdateChecksum();
219 }
220
GetChecksum() const221 Crc32 UsageStore::GetChecksum() const {
222 return usage_score_cache_->GetChecksum();
223 }
224
GetElementsFileSize() const225 libtextclassifier3::StatusOr<int64_t> UsageStore::GetElementsFileSize() const {
226 return usage_score_cache_->GetElementsFileSize();
227 }
228
GetDiskUsage() const229 libtextclassifier3::StatusOr<int64_t> UsageStore::GetDiskUsage() const {
230 return usage_score_cache_->GetDiskUsage();
231 }
232
TruncateTo(DocumentId num_documents)233 libtextclassifier3::Status UsageStore::TruncateTo(DocumentId num_documents) {
234 if (num_documents >= usage_score_cache_->num_elements()) {
235 // No need to truncate
236 return libtextclassifier3::Status::OK;
237 }
238 // "+1" because document ids start from 0.
239 return usage_score_cache_->TruncateTo(num_documents);
240 }
241
Reset()242 libtextclassifier3::Status UsageStore::Reset() {
243 // We delete all the scores by deleting the whole file.
244 libtextclassifier3::Status status = FileBackedVector<int64_t>::Delete(
245 filesystem_, MakeUsageScoreCacheFilename(base_dir_));
246 if (!status.ok()) {
247 ICING_LOG(ERROR) << status.error_message()
248 << "Failed to delete usage_score_cache";
249 return status;
250 }
251
252 // Create a new usage_score_cache
253 auto usage_score_cache_or = FileBackedVector<UsageScores>::Create(
254 filesystem_, MakeUsageScoreCacheFilename(base_dir_),
255 MemoryMappedFile::READ_WRITE_AUTO_SYNC);
256 if (!usage_score_cache_or.ok()) {
257 ICING_LOG(ERROR) << usage_score_cache_or.status().error_message()
258 << "Failed to re-create usage_score_cache";
259 return usage_score_cache_or.status();
260 }
261 usage_score_cache_ = std::move(usage_score_cache_or).ValueOrDie();
262
263 return PersistToDisk();
264 }
265
266 } // namespace lib
267 } // namespace icing
268