file_system_usage_cache.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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 "webkit/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/file_util.h"
12#include "base/files/file_path.h"
13#include "base/pickle.h"
14#include "base/stl_util.h"
15#include "webkit/browser/fileapi/timed_task_helper.h"
16
17namespace fileapi {
18
19namespace {
20const int64 kCloseDelaySeconds = 5;
21const size_t kMaxHandleCacheSize = 2;
22}  // namespace
23
24FileSystemUsageCache::FileSystemUsageCache(
25    base::SequencedTaskRunner* task_runner)
26    : weak_factory_(this),
27      task_runner_(task_runner) {
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  for (CacheFiles::iterator itr = cache_files_.begin();
166       itr != cache_files_.end(); ++itr) {
167    if (itr->second != base::kInvalidPlatformFileValue)
168      base::ClosePlatformFile(itr->second);
169  }
170  cache_files_.clear();
171  timer_.reset();
172}
173
174bool FileSystemUsageCache::Read(const base::FilePath& usage_file_path,
175                                 bool* is_valid,
176                                 uint32* dirty_out,
177                                 int64* usage_out) {
178  TRACE_EVENT0("FileSystem", "UsageCache::Read");
179  DCHECK(CalledOnValidThread());
180  DCHECK(is_valid);
181  DCHECK(dirty_out);
182  DCHECK(usage_out);
183  char buffer[kUsageFileSize];
184  const char *header;
185  if (usage_file_path.empty() ||
186      !ReadBytes(usage_file_path, buffer, kUsageFileSize))
187    return false;
188  Pickle read_pickle(buffer, kUsageFileSize);
189  PickleIterator iter(read_pickle);
190  uint32 dirty = 0;
191  int64 usage = 0;
192
193  if (!read_pickle.ReadBytes(&iter, &header, kUsageFileHeaderSize) ||
194      !read_pickle.ReadBool(&iter, is_valid) ||
195      !read_pickle.ReadUInt32(&iter, &dirty) ||
196      !read_pickle.ReadInt64(&iter, &usage))
197    return false;
198
199  if (header[0] != kUsageFileHeader[0] ||
200      header[1] != kUsageFileHeader[1] ||
201      header[2] != kUsageFileHeader[2] ||
202      header[3] != kUsageFileHeader[3])
203    return false;
204
205  *dirty_out = dirty;
206  *usage_out = usage;
207  return true;
208}
209
210bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path,
211                                 bool is_valid,
212                                 int32 dirty,
213                                 int64 usage) {
214  TRACE_EVENT0("FileSystem", "UsageCache::Write");
215  DCHECK(CalledOnValidThread());
216  Pickle write_pickle;
217  write_pickle.WriteBytes(kUsageFileHeader, kUsageFileHeaderSize);
218  write_pickle.WriteBool(is_valid);
219  write_pickle.WriteUInt32(dirty);
220  write_pickle.WriteInt64(usage);
221
222  if (!WriteBytes(usage_file_path,
223                  static_cast<const char*>(write_pickle.data()),
224                  write_pickle.size())) {
225    Delete(usage_file_path);
226    return false;
227  }
228  return true;
229}
230
231bool FileSystemUsageCache::GetPlatformFile(const base::FilePath& file_path,
232                                           base::PlatformFile* file) {
233  DCHECK(CalledOnValidThread());
234  if (cache_files_.size() >= kMaxHandleCacheSize)
235    CloseCacheFiles();
236  ScheduleCloseTimer();
237
238  std::pair<CacheFiles::iterator, bool> inserted =
239      cache_files_.insert(
240          std::make_pair(file_path, base::kInvalidPlatformFileValue));
241  if (!inserted.second) {
242    *file = inserted.first->second;
243    return true;
244  }
245
246  base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
247  base::PlatformFile platform_file =
248      base::CreatePlatformFile(file_path,
249                               base::PLATFORM_FILE_OPEN_ALWAYS |
250                               base::PLATFORM_FILE_READ |
251                               base::PLATFORM_FILE_WRITE,
252                               NULL, &error);
253  if (error != base::PLATFORM_FILE_OK) {
254    cache_files_.erase(inserted.first);
255    return false;
256  }
257
258  inserted.first->second = platform_file;
259  *file = platform_file;
260  return true;
261}
262
263bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path,
264                                     char* buffer,
265                                     int64 buffer_size) {
266  DCHECK(CalledOnValidThread());
267  base::PlatformFile file;
268  if (!GetPlatformFile(file_path, &file))
269    return false;
270  return base::ReadPlatformFile(file, 0, buffer, buffer_size) == buffer_size;
271}
272
273bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path,
274                                      const char* buffer,
275                                      int64 buffer_size) {
276  DCHECK(CalledOnValidThread());
277  base::PlatformFile file;
278  if (!GetPlatformFile(file_path, &file))
279    return false;
280  return base::WritePlatformFile(file, 0, buffer, buffer_size) == buffer_size;
281}
282
283bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) {
284  TRACE_EVENT0("FileSystem", "UsageCache::FlushFile");
285  DCHECK(CalledOnValidThread());
286  base::PlatformFile file = base::kInvalidPlatformFileValue;
287  return GetPlatformFile(file_path, &file) && base::FlushPlatformFile(file);
288}
289
290void FileSystemUsageCache::ScheduleCloseTimer() {
291  DCHECK(CalledOnValidThread());
292  if (!timer_)
293    timer_.reset(new TimedTaskHelper(task_runner_.get()));
294
295  if (timer_->IsRunning()) {
296    timer_->Reset();
297    return;
298  }
299
300  timer_->Start(FROM_HERE,
301                base::TimeDelta::FromSeconds(kCloseDelaySeconds),
302                base::Bind(&FileSystemUsageCache::CloseCacheFiles,
303                           weak_factory_.GetWeakPtr()));
304}
305
306bool FileSystemUsageCache::CalledOnValidThread() {
307  return !task_runner_.get() || task_runner_->RunsTasksOnCurrentThread();
308}
309
310bool FileSystemUsageCache::HasCacheFileHandle(const base::FilePath& file_path) {
311  DCHECK(CalledOnValidThread());
312  DCHECK_LE(cache_files_.size(), kMaxHandleCacheSize);
313  return ContainsKey(cache_files_, file_path);
314}
315
316}  // namespace fileapi
317