rlz_value_store_chromeos.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "rlz/chromeos/lib/rlz_value_store_chromeos.h"
6
7#include "base/file_util.h"
8#include "base/files/important_file_writer.h"
9#include "base/json/json_file_value_serializer.h"
10#include "base/json/json_string_value_serializer.h"
11#include "base/logging.h"
12#include "base/sequenced_task_runner.h"
13#include "base/strings/string_number_conversions.h"
14#include "base/values.h"
15#include "rlz/lib/lib_values.h"
16#include "rlz/lib/recursive_cross_process_lock_posix.h"
17#include "rlz/lib/rlz_lib.h"
18
19namespace rlz_lib {
20
21namespace {
22
23// Key names.
24const char kPingTimeKey[] = "ping_time";
25const char kAccessPointKey[] = "access_points";
26const char kProductEventKey[] = "product_events";
27const char kStatefulEventKey[] = "stateful_events";
28
29// Brand name used when there is no supplementary brand name.
30const char kNoSupplementaryBrand[] = "_";
31
32// RLZ store filename.
33const base::FilePath::CharType kRLZDataFileName[] =
34    FILE_PATH_LITERAL("RLZ Data");
35
36// RLZ store lock filename
37const base::FilePath::CharType kRLZLockFileName[] =
38    FILE_PATH_LITERAL("RLZ Data.lock");
39
40// RLZ store path for testing.
41base::FilePath g_testing_rlz_store_path_;
42
43// Returns file path of the RLZ storage.
44base::FilePath GetRlzStorePath() {
45  return g_testing_rlz_store_path_.empty() ?
46      file_util::GetHomeDir().Append(kRLZDataFileName) :
47      g_testing_rlz_store_path_.Append(kRLZDataFileName);
48}
49
50// Returns file path of the RLZ storage lock file.
51base::FilePath GetRlzStoreLockPath() {
52  return g_testing_rlz_store_path_.empty() ?
53      file_util::GetHomeDir().Append(kRLZLockFileName) :
54      g_testing_rlz_store_path_.Append(kRLZLockFileName);
55}
56
57// Returns the dictionary key for storing access point-related prefs.
58std::string GetKeyName(std::string key, AccessPoint access_point) {
59  std::string brand = SupplementaryBranding::GetBrand();
60  if (brand.empty())
61    brand = kNoSupplementaryBrand;
62  return key + "." + GetAccessPointName(access_point) + "." + brand;
63}
64
65// Returns the dictionary key for storing product-related prefs.
66std::string GetKeyName(std::string key, Product product) {
67  std::string brand = SupplementaryBranding::GetBrand();
68  if (brand.empty())
69    brand = kNoSupplementaryBrand;
70  return key + "." + GetProductName(product) + "." + brand;
71}
72
73}  // namespace
74
75RlzValueStoreChromeOS::RlzValueStoreChromeOS(const base::FilePath& store_path)
76    : rlz_store_(new base::DictionaryValue),
77      store_path_(store_path),
78      read_only_(true) {
79  ReadStore();
80}
81
82RlzValueStoreChromeOS::~RlzValueStoreChromeOS() {
83  WriteStore();
84}
85
86bool RlzValueStoreChromeOS::HasAccess(AccessType type) {
87  DCHECK(CalledOnValidThread());
88  return type == kReadAccess || !read_only_;
89}
90
91bool RlzValueStoreChromeOS::WritePingTime(Product product, int64 time) {
92  DCHECK(CalledOnValidThread());
93  rlz_store_->SetString(GetKeyName(kPingTimeKey, product),
94                        base::Int64ToString(time));
95  return true;
96}
97
98bool RlzValueStoreChromeOS::ReadPingTime(Product product, int64* time) {
99  DCHECK(CalledOnValidThread());
100  std::string ping_time;
101  return rlz_store_->GetString(GetKeyName(kPingTimeKey, product), &ping_time) &&
102      base::StringToInt64(ping_time, time);
103}
104
105bool RlzValueStoreChromeOS::ClearPingTime(Product product) {
106  DCHECK(CalledOnValidThread());
107  rlz_store_->Remove(GetKeyName(kPingTimeKey, product), NULL);
108  return true;
109}
110
111bool RlzValueStoreChromeOS::WriteAccessPointRlz(AccessPoint access_point,
112                                                const char* new_rlz) {
113  DCHECK(CalledOnValidThread());
114  rlz_store_->SetString(
115      GetKeyName(kAccessPointKey, access_point), new_rlz);
116  return true;
117}
118
119bool RlzValueStoreChromeOS::ReadAccessPointRlz(AccessPoint access_point,
120                                               char* rlz,
121                                               size_t rlz_size) {
122  DCHECK(CalledOnValidThread());
123  std::string rlz_value;
124  rlz_store_->GetString(GetKeyName(kAccessPointKey, access_point), &rlz_value);
125  if (rlz_value.size() < rlz_size) {
126    strncpy(rlz, rlz_value.c_str(), rlz_size);
127    return true;
128  }
129  if (rlz_size > 0)
130    *rlz = '\0';
131  return false;
132}
133
134bool RlzValueStoreChromeOS::ClearAccessPointRlz(AccessPoint access_point) {
135  DCHECK(CalledOnValidThread());
136  rlz_store_->Remove(GetKeyName(kAccessPointKey, access_point), NULL);
137  return true;
138}
139
140bool RlzValueStoreChromeOS::AddProductEvent(Product product,
141                                            const char* event_rlz) {
142  DCHECK(CalledOnValidThread());
143  return AddValueToList(GetKeyName(kProductEventKey, product),
144                        base::Value::CreateStringValue(event_rlz));
145}
146
147bool RlzValueStoreChromeOS::ReadProductEvents(
148    Product product,
149    std::vector<std::string>* events) {
150  DCHECK(CalledOnValidThread());
151  base::ListValue* events_list = NULL; ;
152  if (!rlz_store_->GetList(GetKeyName(kProductEventKey, product), &events_list))
153    return false;
154  events->clear();
155  for (size_t i = 0; i < events_list->GetSize(); ++i) {
156    std::string event;
157    if (events_list->GetString(i, &event))
158      events->push_back(event);
159  }
160  return true;
161}
162
163bool RlzValueStoreChromeOS::ClearProductEvent(Product product,
164                                              const char* event_rlz) {
165  DCHECK(CalledOnValidThread());
166  base::StringValue event_value(event_rlz);
167  return RemoveValueFromList(GetKeyName(kProductEventKey, product),
168                             event_value);
169}
170
171bool RlzValueStoreChromeOS::ClearAllProductEvents(Product product) {
172  DCHECK(CalledOnValidThread());
173  rlz_store_->Remove(GetKeyName(kProductEventKey, product), NULL);
174  return true;
175}
176
177bool RlzValueStoreChromeOS::AddStatefulEvent(Product product,
178                                             const char* event_rlz) {
179  DCHECK(CalledOnValidThread());
180  return AddValueToList(GetKeyName(kStatefulEventKey, product),
181                        base::Value::CreateStringValue(event_rlz));
182}
183
184bool RlzValueStoreChromeOS::IsStatefulEvent(Product product,
185                                            const char* event_rlz) {
186  DCHECK(CalledOnValidThread());
187  base::StringValue event_value(event_rlz);
188  base::ListValue* events_list = NULL;
189  return rlz_store_->GetList(GetKeyName(kStatefulEventKey, product),
190                             &events_list) &&
191      events_list->Find(event_value) != events_list->end();
192}
193
194bool RlzValueStoreChromeOS::ClearAllStatefulEvents(Product product) {
195  DCHECK(CalledOnValidThread());
196  rlz_store_->Remove(GetKeyName(kStatefulEventKey, product), NULL);
197  return true;
198}
199
200void RlzValueStoreChromeOS::CollectGarbage() {
201  DCHECK(CalledOnValidThread());
202  NOTIMPLEMENTED();
203}
204
205void RlzValueStoreChromeOS::ReadStore() {
206  int error_code = 0;
207  std::string error_msg;
208  JSONFileValueSerializer serializer(store_path_);
209  scoped_ptr<base::Value> value(
210      serializer.Deserialize(&error_code, &error_msg));
211  switch (error_code) {
212    case JSONFileValueSerializer::JSON_NO_SUCH_FILE:
213      read_only_ = false;
214      break;
215    case JSONFileValueSerializer::JSON_NO_ERROR:
216      read_only_ = false;
217      rlz_store_.reset(static_cast<base::DictionaryValue*>(value.release()));
218      break;
219    default:
220      LOG(ERROR) << "Error reading RLZ store: " << error_msg;
221  }
222}
223
224void RlzValueStoreChromeOS::WriteStore() {
225  std::string json_data;
226  JSONStringValueSerializer serializer(&json_data);
227  serializer.set_pretty_print(true);
228  scoped_ptr<DictionaryValue> copy(rlz_store_->DeepCopyWithoutEmptyChildren());
229  if (!serializer.Serialize(*copy.get())) {
230    LOG(ERROR) << "Failed to serialize RLZ data";
231    NOTREACHED();
232    return;
233  }
234  if (!base::ImportantFileWriter::WriteFileAtomically(store_path_, json_data))
235    LOG(ERROR) << "Error writing RLZ store";
236}
237
238bool RlzValueStoreChromeOS::AddValueToList(std::string list_name,
239                                           base::Value* value) {
240  base::ListValue* list_value = NULL;
241  if (!rlz_store_->GetList(list_name, &list_value)) {
242    list_value = new base::ListValue;
243    rlz_store_->Set(list_name, list_value);
244  }
245  list_value->AppendIfNotPresent(value);
246  return true;
247}
248
249bool RlzValueStoreChromeOS::RemoveValueFromList(std::string list_name,
250                                                const base::Value& value) {
251  base::ListValue* list_value = NULL;
252  if (!rlz_store_->GetList(list_name, &list_value))
253    return false;
254  size_t index;
255  list_value->Remove(value, &index);
256  return true;
257}
258
259namespace {
260
261// RlzValueStoreChromeOS keeps its data in memory and only writes it to disk
262// when ScopedRlzValueStoreLock goes out of scope. Hence, if several
263// ScopedRlzValueStoreLocks are nested, they all need to use the same store
264// object.
265
266RecursiveCrossProcessLock g_recursive_lock =
267    RECURSIVE_CROSS_PROCESS_LOCK_INITIALIZER;
268
269// This counts the nesting depth of |ScopedRlzValueStoreLock|.
270int g_lock_depth = 0;
271
272// This is the shared store object. Non-|NULL| only when |g_lock_depth > 0|.
273RlzValueStoreChromeOS* g_store = NULL;
274
275}  // namespace
276
277ScopedRlzValueStoreLock::ScopedRlzValueStoreLock() {
278  bool got_cross_process_lock =
279      g_recursive_lock.TryGetCrossProcessLock(GetRlzStoreLockPath());
280  // At this point, we hold the in-process lock, no matter the value of
281  // |got_cross_process_lock|.
282
283  ++g_lock_depth;
284  if (!got_cross_process_lock) {
285    // Acquiring cross-process lock failed, so simply return here.
286    // In-process lock will be released in dtor.
287    DCHECK(!g_store);
288    return;
289  }
290
291  if (g_lock_depth > 1) {
292    // Reuse the already existing store object.
293    DCHECK(g_store);
294    store_.reset(g_store);
295    return;
296  }
297
298  // This is the topmost lock, create a new store object.
299  DCHECK(!g_store);
300  g_store = new RlzValueStoreChromeOS(GetRlzStorePath());
301  store_.reset(g_store);
302}
303
304ScopedRlzValueStoreLock::~ScopedRlzValueStoreLock() {
305  --g_lock_depth;
306  DCHECK(g_lock_depth >= 0);
307
308  if (g_lock_depth > 0) {
309    // Other locks are still using store_, so don't free it yet.
310    ignore_result(store_.release());
311    return;
312  }
313
314  g_store = NULL;
315
316  g_recursive_lock.ReleaseLock();
317}
318
319RlzValueStore* ScopedRlzValueStoreLock::GetStore() {
320  return store_.get();
321}
322
323namespace testing {
324
325void SetRlzStoreDirectory(const base::FilePath& directory) {
326  g_testing_rlz_store_path_ = directory;
327}
328
329std::string RlzStoreFilenameStr() {
330  return GetRlzStorePath().value();
331}
332
333}  // namespace testing
334
335}  // namespace rlz_lib
336