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