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 "storage/browser/fileapi/file_system_usage_cache.h"
6
7#include <utility>
8
9#include "base/bind.h"
10#include "base/debug/trace_event.h"
11#include "base/files/file_path.h"
12#include "base/files/file_util.h"
13#include "base/pickle.h"
14#include "base/stl_util.h"
15#include "storage/browser/fileapi/timed_task_helper.h"
16
17namespace storage {
18
19namespace {
20const int64 kCloseDelaySeconds = 5;
21const size_t kMaxHandleCacheSize = 2;
22}  // namespace
23
24FileSystemUsageCache::FileSystemUsageCache(
25    base::SequencedTaskRunner* task_runner)
26    : task_runner_(task_runner),
27      weak_factory_(this) {
28}
29
30FileSystemUsageCache::~FileSystemUsageCache() {
31  task_runner_ = NULL;
32  CloseCacheFiles();
33}
34
35const base::FilePath::CharType FileSystemUsageCache::kUsageFileName[] =
36    FILE_PATH_LITERAL(".usage");
37const char FileSystemUsageCache::kUsageFileHeader[] = "FSU5";
38const int FileSystemUsageCache::kUsageFileHeaderSize = 4;
39
40// Pickle::{Read,Write}Bool treat bool as int
41const int FileSystemUsageCache::kUsageFileSize =
42    sizeof(Pickle::Header) +
43    FileSystemUsageCache::kUsageFileHeaderSize +
44    sizeof(int) + sizeof(int32) + sizeof(int64);  // NOLINT
45
46bool FileSystemUsageCache::GetUsage(const base::FilePath& usage_file_path,
47                                    int64* usage_out) {
48  TRACE_EVENT0("FileSystem", "UsageCache::GetUsage");
49  DCHECK(CalledOnValidThread());
50  DCHECK(usage_out);
51  bool is_valid = true;
52  uint32 dirty = 0;
53  int64 usage = 0;
54  if (!Read(usage_file_path, &is_valid, &dirty, &usage))
55    return false;
56  *usage_out = usage;
57  return true;
58}
59
60bool FileSystemUsageCache::GetDirty(const base::FilePath& usage_file_path,
61                                    uint32* dirty_out) {
62  TRACE_EVENT0("FileSystem", "UsageCache::GetDirty");
63  DCHECK(CalledOnValidThread());
64  DCHECK(dirty_out);
65  bool is_valid = true;
66  uint32 dirty = 0;
67  int64 usage = 0;
68  if (!Read(usage_file_path, &is_valid, &dirty, &usage))
69    return false;
70  *dirty_out = dirty;
71  return true;
72}
73
74bool FileSystemUsageCache::IncrementDirty(
75    const base::FilePath& usage_file_path) {
76  TRACE_EVENT0("FileSystem", "UsageCache::IncrementDirty");
77  DCHECK(CalledOnValidThread());
78  bool is_valid = true;
79  uint32 dirty = 0;
80  int64 usage = 0;
81  bool new_handle = !HasCacheFileHandle(usage_file_path);
82  if (!Read(usage_file_path, &is_valid, &dirty, &usage))
83    return false;
84
85  bool success = Write(usage_file_path, is_valid, dirty + 1, usage);
86  if (success && dirty == 0 && new_handle)
87    FlushFile(usage_file_path);
88  return success;
89}
90
91bool FileSystemUsageCache::DecrementDirty(
92    const base::FilePath& usage_file_path) {
93  TRACE_EVENT0("FileSystem", "UsageCache::DecrementDirty");
94  DCHECK(CalledOnValidThread());
95  bool is_valid = true;
96  uint32 dirty = 0;
97  int64 usage = 0;
98  if (!Read(usage_file_path, &is_valid, &dirty, &usage) || dirty <= 0)
99    return false;
100
101  if (dirty <= 0)
102    return false;
103
104  return Write(usage_file_path, is_valid, dirty - 1, usage);
105}
106
107bool FileSystemUsageCache::Invalidate(const base::FilePath& usage_file_path) {
108  TRACE_EVENT0("FileSystem", "UsageCache::Invalidate");
109  DCHECK(CalledOnValidThread());
110  bool is_valid = true;
111  uint32 dirty = 0;
112  int64 usage = 0;
113  if (!Read(usage_file_path, &is_valid, &dirty, &usage))
114    return false;
115
116  return Write(usage_file_path, false, dirty, usage);
117}
118
119bool FileSystemUsageCache::IsValid(const base::FilePath& usage_file_path) {
120  TRACE_EVENT0("FileSystem", "UsageCache::IsValid");
121  DCHECK(CalledOnValidThread());
122  bool is_valid = true;
123  uint32 dirty = 0;
124  int64 usage = 0;
125  if (!Read(usage_file_path, &is_valid, &dirty, &usage))
126    return false;
127  return is_valid;
128}
129
130bool FileSystemUsageCache::AtomicUpdateUsageByDelta(
131    const base::FilePath& usage_file_path, int64 delta) {
132  TRACE_EVENT0("FileSystem", "UsageCache::AtomicUpdateUsageByDelta");
133  DCHECK(CalledOnValidThread());
134  bool is_valid = true;
135  uint32 dirty = 0;
136  int64 usage = 0;;
137  if (!Read(usage_file_path, &is_valid, &dirty, &usage))
138    return false;
139  return Write(usage_file_path, is_valid, dirty, usage + delta);
140}
141
142bool FileSystemUsageCache::UpdateUsage(const base::FilePath& usage_file_path,
143                                       int64 fs_usage) {
144  TRACE_EVENT0("FileSystem", "UsageCache::UpdateUsage");
145  DCHECK(CalledOnValidThread());
146  return Write(usage_file_path, true, 0, fs_usage);
147}
148
149bool FileSystemUsageCache::Exists(const base::FilePath& usage_file_path) {
150  TRACE_EVENT0("FileSystem", "UsageCache::Exists");
151  DCHECK(CalledOnValidThread());
152  return base::PathExists(usage_file_path);
153}
154
155bool FileSystemUsageCache::Delete(const base::FilePath& usage_file_path) {
156  TRACE_EVENT0("FileSystem", "UsageCache::Delete");
157  DCHECK(CalledOnValidThread());
158  CloseCacheFiles();
159  return base::DeleteFile(usage_file_path, true);
160}
161
162void FileSystemUsageCache::CloseCacheFiles() {
163  TRACE_EVENT0("FileSystem", "UsageCache::CloseCacheFiles");
164  DCHECK(CalledOnValidThread());
165  STLDeleteValues(&cache_files_);
166  timer_.reset();
167}
168
169bool FileSystemUsageCache::Read(const base::FilePath& usage_file_path,
170                                 bool* is_valid,
171                                 uint32* dirty_out,
172                                 int64* usage_out) {
173  TRACE_EVENT0("FileSystem", "UsageCache::Read");
174  DCHECK(CalledOnValidThread());
175  DCHECK(is_valid);
176  DCHECK(dirty_out);
177  DCHECK(usage_out);
178  char buffer[kUsageFileSize];
179  const char *header;
180  if (usage_file_path.empty() ||
181      !ReadBytes(usage_file_path, buffer, kUsageFileSize))
182    return false;
183  Pickle read_pickle(buffer, kUsageFileSize);
184  PickleIterator iter(read_pickle);
185  uint32 dirty = 0;
186  int64 usage = 0;
187
188  if (!read_pickle.ReadBytes(&iter, &header, kUsageFileHeaderSize) ||
189      !read_pickle.ReadBool(&iter, is_valid) ||
190      !read_pickle.ReadUInt32(&iter, &dirty) ||
191      !read_pickle.ReadInt64(&iter, &usage))
192    return false;
193
194  if (header[0] != kUsageFileHeader[0] ||
195      header[1] != kUsageFileHeader[1] ||
196      header[2] != kUsageFileHeader[2] ||
197      header[3] != kUsageFileHeader[3])
198    return false;
199
200  *dirty_out = dirty;
201  *usage_out = usage;
202  return true;
203}
204
205bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path,
206                                 bool is_valid,
207                                 int32 dirty,
208                                 int64 usage) {
209  TRACE_EVENT0("FileSystem", "UsageCache::Write");
210  DCHECK(CalledOnValidThread());
211  Pickle write_pickle;
212  write_pickle.WriteBytes(kUsageFileHeader, kUsageFileHeaderSize);
213  write_pickle.WriteBool(is_valid);
214  write_pickle.WriteUInt32(dirty);
215  write_pickle.WriteInt64(usage);
216
217  if (!WriteBytes(usage_file_path,
218                  static_cast<const char*>(write_pickle.data()),
219                  write_pickle.size())) {
220    Delete(usage_file_path);
221    return false;
222  }
223  return true;
224}
225
226base::File* FileSystemUsageCache::GetFile(const base::FilePath& file_path) {
227  DCHECK(CalledOnValidThread());
228  if (cache_files_.size() >= kMaxHandleCacheSize)
229    CloseCacheFiles();
230  ScheduleCloseTimer();
231
232  base::File* new_file = NULL;
233  std::pair<CacheFiles::iterator, bool> inserted =
234      cache_files_.insert(std::make_pair(file_path, new_file));
235  if (!inserted.second)
236    return inserted.first->second;
237
238  new_file = new base::File(file_path,
239                            base::File::FLAG_OPEN_ALWAYS |
240                            base::File::FLAG_READ |
241                            base::File::FLAG_WRITE);
242  if (!new_file->IsValid()) {
243    cache_files_.erase(inserted.first);
244    delete new_file;
245    return NULL;
246  }
247
248  inserted.first->second = new_file;
249  return new_file;
250}
251
252bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path,
253                                     char* buffer,
254                                     int64 buffer_size) {
255  DCHECK(CalledOnValidThread());
256  base::File* file = GetFile(file_path);
257  if (!file)
258    return false;
259  return file->Read(0, buffer, buffer_size) == buffer_size;
260}
261
262bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path,
263                                      const char* buffer,
264                                      int64 buffer_size) {
265  DCHECK(CalledOnValidThread());
266  base::File* file = GetFile(file_path);
267  if (!file)
268    return false;
269  return file->Write(0, buffer, buffer_size) == buffer_size;
270}
271
272bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) {
273  TRACE_EVENT0("FileSystem", "UsageCache::FlushFile");
274  DCHECK(CalledOnValidThread());
275  base::File* file = GetFile(file_path);
276  if (!file)
277    return false;
278  return file->Flush();
279}
280
281void FileSystemUsageCache::ScheduleCloseTimer() {
282  DCHECK(CalledOnValidThread());
283  if (!timer_)
284    timer_.reset(new TimedTaskHelper(task_runner_.get()));
285
286  if (timer_->IsRunning()) {
287    timer_->Reset();
288    return;
289  }
290
291  timer_->Start(FROM_HERE,
292                base::TimeDelta::FromSeconds(kCloseDelaySeconds),
293                base::Bind(&FileSystemUsageCache::CloseCacheFiles,
294                           weak_factory_.GetWeakPtr()));
295}
296
297bool FileSystemUsageCache::CalledOnValidThread() {
298  return !task_runner_.get() || task_runner_->RunsTasksOnCurrentThread();
299}
300
301bool FileSystemUsageCache::HasCacheFileHandle(const base::FilePath& file_path) {
302  DCHECK(CalledOnValidThread());
303  DCHECK_LE(cache_files_.size(), kMaxHandleCacheSize);
304  return ContainsKey(cache_files_, file_path);
305}
306
307}  // namespace storage
308