xref: /aosp_15_r20/external/icing/icing/store/usage-store.cc (revision 8b6cd535a057e39b3b86660c4aa06c99747c2136)
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