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