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 "chrome/browser/chromeos/drive/file_cache_metadata.h"
6
7#include "base/callback.h"
8#include "base/file_util.h"
9#include "base/files/file_enumerator.h"
10#include "base/metrics/histogram.h"
11#include "base/sequenced_task_runner.h"
12#include "base/threading/thread_restrictions.h"
13#include "chrome/browser/chromeos/drive/drive.pb.h"
14#include "chrome/browser/chromeos/drive/file_system_util.h"
15#include "third_party/leveldatabase/src/include/leveldb/db.h"
16
17namespace drive {
18namespace internal {
19
20namespace {
21
22enum DBOpenStatus {
23  DB_OPEN_SUCCESS,
24  DB_OPEN_FAILURE_CORRUPTION,
25  DB_OPEN_FAILURE_OTHER,
26  DB_OPEN_FAILURE_UNRECOVERABLE,
27  DB_OPEN_MAX_VALUE,
28};
29
30}  // namespace
31
32FileCacheMetadata::Iterator::Iterator(scoped_ptr<leveldb::Iterator> it)
33    : it_(it.Pass()) {
34  base::ThreadRestrictions::AssertIOAllowed();
35  DCHECK(it_);
36
37  it_->SeekToFirst();
38  AdvanceInternal();
39}
40
41FileCacheMetadata::Iterator::~Iterator() {
42  base::ThreadRestrictions::AssertIOAllowed();
43}
44
45bool FileCacheMetadata::Iterator::IsAtEnd() const {
46  base::ThreadRestrictions::AssertIOAllowed();
47  return !it_->Valid();
48}
49
50std::string FileCacheMetadata::Iterator::GetKey() const {
51  base::ThreadRestrictions::AssertIOAllowed();
52  DCHECK(!IsAtEnd());
53  return it_->key().ToString();
54}
55
56const FileCacheEntry& FileCacheMetadata::Iterator::GetValue() const {
57  base::ThreadRestrictions::AssertIOAllowed();
58  DCHECK(!IsAtEnd());
59  return entry_;
60}
61
62void FileCacheMetadata::Iterator::Advance() {
63  base::ThreadRestrictions::AssertIOAllowed();
64  DCHECK(!IsAtEnd());
65
66  it_->Next();
67  AdvanceInternal();
68}
69
70bool FileCacheMetadata::Iterator::HasError() const {
71  base::ThreadRestrictions::AssertIOAllowed();
72  return !it_->status().ok();
73}
74
75void FileCacheMetadata::Iterator::AdvanceInternal() {
76  for (; it_->Valid(); it_->Next()) {
77    // Skip unparsable broken entries.
78    // TODO(hashimoto): Broken entries should be cleaned up at some point.
79    if (entry_.ParseFromArray(it_->value().data(), it_->value().size()))
80      break;
81  }
82}
83
84FileCacheMetadata::FileCacheMetadata(
85    base::SequencedTaskRunner* blocking_task_runner)
86    : blocking_task_runner_(blocking_task_runner) {
87  AssertOnSequencedWorkerPool();
88}
89
90FileCacheMetadata::~FileCacheMetadata() {
91  AssertOnSequencedWorkerPool();
92}
93
94FileCacheMetadata::InitializeResult FileCacheMetadata::Initialize(
95    const base::FilePath& db_path) {
96  AssertOnSequencedWorkerPool();
97
98  bool created = !base::PathExists(db_path);
99
100  leveldb::DB* level_db = NULL;
101  leveldb::Options options;
102  options.create_if_missing = true;
103  options.max_open_files = 64;  // Use minimum.
104  leveldb::Status db_status = leveldb::DB::Open(options, db_path.AsUTF8Unsafe(),
105                                                &level_db);
106
107  // Delete the db and scan the physical cache. This will fix a corrupt db, but
108  // perhaps not other causes of failed DB::Open.
109  DBOpenStatus uma_status = DB_OPEN_SUCCESS;
110  if (!db_status.ok()) {
111    LOG(WARNING) << "Cache db failed to open: " << db_status.ToString();
112    uma_status = db_status.IsCorruption() ?
113        DB_OPEN_FAILURE_CORRUPTION : DB_OPEN_FAILURE_OTHER;
114    const bool deleted = base::DeleteFile(db_path, true);
115    DCHECK(deleted);
116    db_status = leveldb::DB::Open(options, db_path.value(), &level_db);
117    if (!db_status.ok()) {
118      LOG(WARNING) << "Still failed to open: " << db_status.ToString();
119      UMA_HISTOGRAM_ENUMERATION("Drive.CacheDBOpenStatus",
120                                DB_OPEN_FAILURE_UNRECOVERABLE,
121                                DB_OPEN_MAX_VALUE);
122      return INITIALIZE_FAILED;
123    }
124
125    created = true;
126  }
127  UMA_HISTOGRAM_ENUMERATION("Drive.CacheDBOpenStatus", uma_status,
128                            DB_OPEN_MAX_VALUE);
129  DCHECK(level_db);
130  level_db_.reset(level_db);
131
132  return created ? INITIALIZE_CREATED : INITIALIZE_OPENED;
133}
134
135void FileCacheMetadata::AddOrUpdateCacheEntry(
136    const std::string& resource_id,
137    const FileCacheEntry& cache_entry) {
138  AssertOnSequencedWorkerPool();
139
140  DVLOG(1) << "AddOrUpdateCacheEntry, resource_id=" << resource_id;
141  std::string serialized;
142  const bool ok = cache_entry.SerializeToString(&serialized);
143  if (ok)
144    level_db_->Put(leveldb::WriteOptions(),
145                   leveldb::Slice(resource_id),
146                   leveldb::Slice(serialized));
147}
148
149void FileCacheMetadata::RemoveCacheEntry(const std::string& resource_id) {
150  AssertOnSequencedWorkerPool();
151
152  DVLOG(1) << "RemoveCacheEntry, resource_id=" << resource_id;
153  level_db_->Delete(leveldb::WriteOptions(), leveldb::Slice(resource_id));
154}
155
156bool FileCacheMetadata::GetCacheEntry(const std::string& resource_id,
157                                      FileCacheEntry* entry) {
158  DCHECK(entry);
159  AssertOnSequencedWorkerPool();
160
161  std::string serialized;
162  const leveldb::Status status = level_db_->Get(
163      leveldb::ReadOptions(),
164      leveldb::Slice(resource_id), &serialized);
165  if (!status.ok()) {
166    DVLOG(1) << "Can't find " << resource_id << " in cache db";
167    return false;
168  }
169
170  if (!entry->ParseFromString(serialized)) {
171    LOG(ERROR) << "Failed to parse " << serialized;
172    return false;
173  }
174  return true;
175}
176
177scoped_ptr<FileCacheMetadata::Iterator> FileCacheMetadata::GetIterator() {
178  AssertOnSequencedWorkerPool();
179
180  scoped_ptr<leveldb::Iterator> iter(level_db_->NewIterator(
181      leveldb::ReadOptions()));
182  return make_scoped_ptr(new Iterator(iter.Pass()));
183}
184
185void FileCacheMetadata::AssertOnSequencedWorkerPool() {
186  DCHECK(!blocking_task_runner_.get() ||
187         blocking_task_runner_->RunsTasksOnCurrentThread());
188}
189
190}  // namespace internal
191}  // namespace drive
192