file_cache.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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.h"
6
7#include <vector>
8
9#include "base/file_util.h"
10#include "base/files/file_enumerator.h"
11#include "base/logging.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/sys_info.h"
15#include "base/task_runner_util.h"
16#include "chrome/browser/chromeos/drive/drive.pb.h"
17#include "chrome/browser/chromeos/drive/file_cache_metadata.h"
18#include "chrome/browser/chromeos/drive/file_system_util.h"
19#include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
20#include "chrome/browser/google_apis/task_util.h"
21#include "chromeos/chromeos_constants.h"
22#include "content/public/browser/browser_thread.h"
23
24using content::BrowserThread;
25
26namespace drive {
27namespace internal {
28namespace {
29
30typedef std::map<std::string, FileCacheEntry> CacheMap;
31
32// Returns true if |md5| matches the one in |cache_entry| with some
33// exceptions. See the function definition for details.
34bool CheckIfMd5Matches(const std::string& md5,
35                       const FileCacheEntry& cache_entry) {
36  if (cache_entry.is_dirty()) {
37    // If the entry is dirty, its MD5 may have been replaced by "local"
38    // during cache initialization, so we don't compare MD5.
39    return true;
40  } else if (cache_entry.is_pinned() && cache_entry.md5().empty()) {
41    // If the entry is pinned, it's ok for the entry to have an empty
42    // MD5. This can happen if the pinned file is not fetched.
43    return true;
44  } else if (md5.empty()) {
45    // If the MD5 matching is not requested, don't check MD5.
46    return true;
47  } else if (md5 == cache_entry.md5()) {
48    // Otherwise, compare the MD5.
49    return true;
50  }
51  return false;
52}
53
54// Scans cache subdirectory and insert found files to |cache_map|.
55void ScanCacheDirectory(const base::FilePath& directory_path,
56                        CacheMap* cache_map) {
57  base::FileEnumerator enumerator(directory_path,
58                                  false,  // not recursive
59                                  base::FileEnumerator::FILES);
60  for (base::FilePath current = enumerator.Next(); !current.empty();
61       current = enumerator.Next()) {
62    // Extract resource_id and md5 from filename.
63    std::string resource_id;
64    std::string md5;
65    util::ParseCacheFilePath(current, &resource_id, &md5);
66
67    // Determine cache state.
68    FileCacheEntry cache_entry;
69    cache_entry.set_md5(md5);
70    cache_entry.set_is_present(true);
71
72    // Add the dirty bit if |md5| indicates that the file is dirty.
73    if (md5 == util::kLocallyModifiedFileExtension)
74      cache_entry.set_is_dirty(true);
75
76    // Create and insert new entry into cache map.
77    cache_map->insert(std::make_pair(resource_id, cache_entry));
78  }
79}
80
81// Moves the file.
82bool MoveFile(const base::FilePath& source_path,
83              const base::FilePath& dest_path) {
84  if (!base::Move(source_path, dest_path)) {
85    LOG(ERROR) << "Failed to move " << source_path.value()
86               << " to " << dest_path.value();
87    return false;
88  }
89  DVLOG(1) << "Moved " << source_path.value() << " to " << dest_path.value();
90  return true;
91}
92
93// Copies the file.
94bool CopyFile(const base::FilePath& source_path,
95              const base::FilePath& dest_path) {
96  if (!file_util::CopyFile(source_path, dest_path)) {
97    LOG(ERROR) << "Failed to copy " << source_path.value()
98               << " to " << dest_path.value();
99    return false;
100  }
101  DVLOG(1) << "Copied " << source_path.value() << " to " << dest_path.value();
102  return true;
103}
104
105// Deletes all files that match |path_to_delete_pattern| except for
106// |path_to_keep| on blocking pool.
107// If |path_to_keep| is empty, all files in |path_to_delete_pattern| are
108// deleted.
109void DeleteFilesSelectively(const base::FilePath& path_to_delete_pattern,
110                            const base::FilePath& path_to_keep) {
111  // Enumerate all files in directory of |path_to_delete_pattern| that match
112  // base name of |path_to_delete_pattern|.
113  // If a file is not |path_to_keep|, delete it.
114  bool success = true;
115  base::FileEnumerator enumerator(
116      path_to_delete_pattern.DirName(),
117      false,  // not recursive
118      base::FileEnumerator::FILES,
119      path_to_delete_pattern.BaseName().value());
120  for (base::FilePath current = enumerator.Next(); !current.empty();
121       current = enumerator.Next()) {
122    // If |path_to_keep| is not empty and same as current, don't delete it.
123    if (!path_to_keep.empty() && current == path_to_keep)
124      continue;
125
126    success = base::Delete(current, false);
127    if (!success)
128      DVLOG(1) << "Error deleting " << current.value();
129    else
130      DVLOG(1) << "Deleted " << current.value();
131  }
132}
133
134// Runs callback with pointers dereferenced.
135// Used to implement GetFile, MarkAsMounted.
136void RunGetFileFromCacheCallback(
137    const GetFileFromCacheCallback& callback,
138    base::FilePath* file_path,
139    FileError error) {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
141  DCHECK(!callback.is_null());
142  DCHECK(file_path);
143
144  callback.Run(error, *file_path);
145}
146
147// Runs callback with pointers dereferenced.
148// Used to implement GetCacheEntry().
149void RunGetCacheEntryCallback(const GetCacheEntryCallback& callback,
150                              FileCacheEntry* cache_entry,
151                              bool success) {
152  DCHECK(cache_entry);
153  DCHECK(!callback.is_null());
154  callback.Run(success, *cache_entry);
155}
156
157// Calls |iteration_callback| with each entry in |cache|.
158void IterateCache(FileCache* cache,
159                  const CacheIterateCallback& iteration_callback) {
160  scoped_ptr<FileCache::Iterator> it = cache->GetIterator();
161  for (; !it->IsAtEnd(); it->Advance())
162    iteration_callback.Run(it->GetID(), it->GetValue());
163  DCHECK(!it->HasError());
164}
165
166}  // namespace
167
168const base::FilePath::CharType FileCache::kOldCacheMetadataDBName[] =
169    FILE_PATH_LITERAL("cache_metadata.db");
170
171FileCache::FileCache(ResourceMetadataStorage* storage,
172                     const base::FilePath& cache_file_directory,
173                     base::SequencedTaskRunner* blocking_task_runner,
174                     FreeDiskSpaceGetterInterface* free_disk_space_getter)
175    : cache_file_directory_(cache_file_directory),
176      blocking_task_runner_(blocking_task_runner),
177      storage_(storage),
178      free_disk_space_getter_(free_disk_space_getter),
179      weak_ptr_factory_(this) {
180  DCHECK(blocking_task_runner_.get());
181  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
182}
183
184FileCache::~FileCache() {
185  // Must be on the sequenced worker pool, as |metadata_| must be deleted on
186  // the sequenced worker pool.
187  AssertOnSequencedWorkerPool();
188}
189
190base::FilePath FileCache::GetCacheFilePath(const std::string& resource_id,
191                                           const std::string& md5,
192                                           CachedFileOrigin file_origin) const {
193  // Runs on any thread.
194  // Filename is formatted as resource_id.md5, i.e. resource_id is the base
195  // name and md5 is the extension.
196  std::string base_name = util::EscapeCacheFileName(resource_id);
197  if (file_origin == CACHED_FILE_LOCALLY_MODIFIED) {
198    base_name += base::FilePath::kExtensionSeparator;
199    base_name += util::kLocallyModifiedFileExtension;
200  } else if (!md5.empty()) {
201    base_name += base::FilePath::kExtensionSeparator;
202    base_name += util::EscapeCacheFileName(md5);
203  }
204  return cache_file_directory_.Append(
205      base::FilePath::FromUTF8Unsafe(base_name));
206}
207
208void FileCache::AssertOnSequencedWorkerPool() {
209  DCHECK(!blocking_task_runner_.get() ||
210         blocking_task_runner_->RunsTasksOnCurrentThread());
211}
212
213bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
214  return cache_file_directory_.IsParent(path);
215}
216
217void FileCache::GetCacheEntryOnUIThread(const std::string& resource_id,
218                                        const std::string& md5,
219                                        const GetCacheEntryCallback& callback) {
220  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
221  DCHECK(!callback.is_null());
222
223  FileCacheEntry* cache_entry = new FileCacheEntry;
224  base::PostTaskAndReplyWithResult(
225      blocking_task_runner_.get(),
226      FROM_HERE,
227      base::Bind(&FileCache::GetCacheEntry,
228                 base::Unretained(this),
229                 resource_id,
230                 md5,
231                 cache_entry),
232      base::Bind(
233          &RunGetCacheEntryCallback, callback, base::Owned(cache_entry)));
234}
235
236bool FileCache::GetCacheEntry(const std::string& resource_id,
237                              const std::string& md5,
238                              FileCacheEntry* entry) {
239  DCHECK(entry);
240  AssertOnSequencedWorkerPool();
241  return storage_->GetCacheEntry(resource_id, entry) &&
242      CheckIfMd5Matches(md5, *entry);
243}
244
245void FileCache::IterateOnUIThread(
246    const CacheIterateCallback& iteration_callback,
247    const base::Closure& completion_callback) {
248  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
249  DCHECK(!iteration_callback.is_null());
250  DCHECK(!completion_callback.is_null());
251
252  blocking_task_runner_->PostTaskAndReply(
253      FROM_HERE,
254      base::Bind(&IterateCache,
255                 base::Unretained(this),
256                 google_apis::CreateRelayCallback(iteration_callback)),
257      completion_callback);
258}
259
260scoped_ptr<FileCache::Iterator> FileCache::GetIterator() {
261  AssertOnSequencedWorkerPool();
262  return storage_->GetCacheEntryIterator();
263}
264
265void FileCache::FreeDiskSpaceIfNeededForOnUIThread(
266    int64 num_bytes,
267    const InitializeCacheCallback& callback) {
268  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
269  DCHECK(!callback.is_null());
270
271  base::PostTaskAndReplyWithResult(
272      blocking_task_runner_.get(),
273      FROM_HERE,
274      base::Bind(&FileCache::FreeDiskSpaceIfNeededFor,
275                 base::Unretained(this),
276                 num_bytes),
277      callback);
278}
279
280bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
281  AssertOnSequencedWorkerPool();
282
283  // Do nothing and return if we have enough space.
284  if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
285    return true;
286
287  // Otherwise, try to free up the disk space.
288  DVLOG(1) << "Freeing up disk space for " << num_bytes;
289
290  // Remove all entries unless specially marked.
291  scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
292      storage_->GetCacheEntryIterator();
293  for (; !it->IsAtEnd(); it->Advance()) {
294    const FileCacheEntry& entry = it->GetValue();
295    if (!entry.is_pinned() &&
296        !entry.is_dirty() &&
297        !mounted_files_.count(it->GetID()))
298      storage_->RemoveCacheEntry(it->GetID());
299  }
300  DCHECK(!it->HasError());
301
302  // Remove all files which have no corresponding cache entries.
303  base::FileEnumerator enumerator(cache_file_directory_,
304                                  false,  // not recursive
305                                  base::FileEnumerator::FILES);
306  std::string resource_id;
307  std::string md5;
308  FileCacheEntry entry;
309  for (base::FilePath current = enumerator.Next(); !current.empty();
310       current = enumerator.Next()) {
311    util::ParseCacheFilePath(current, &resource_id, &md5);
312    if (!GetCacheEntry(resource_id, md5, &entry))
313      base::Delete(current, false /* recursive */);
314  }
315
316  // Check the disk space again.
317  return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
318}
319
320void FileCache::GetFileOnUIThread(const std::string& resource_id,
321                                  const std::string& md5,
322                                  const GetFileFromCacheCallback& callback) {
323  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324  DCHECK(!callback.is_null());
325
326  base::FilePath* cache_file_path = new base::FilePath;
327  base::PostTaskAndReplyWithResult(blocking_task_runner_.get(),
328                                   FROM_HERE,
329                                   base::Bind(&FileCache::GetFile,
330                                              base::Unretained(this),
331                                              resource_id,
332                                              md5,
333                                              cache_file_path),
334                                   base::Bind(&RunGetFileFromCacheCallback,
335                                              callback,
336                                              base::Owned(cache_file_path)));
337}
338
339FileError FileCache::GetFile(const std::string& resource_id,
340                             const std::string& md5,
341                             base::FilePath* cache_file_path) {
342  AssertOnSequencedWorkerPool();
343  DCHECK(cache_file_path);
344
345  FileCacheEntry cache_entry;
346  if (!GetCacheEntry(resource_id, md5, &cache_entry) ||
347      !cache_entry.is_present())
348    return FILE_ERROR_NOT_FOUND;
349
350  CachedFileOrigin file_origin = cache_entry.is_dirty() ?
351      CACHED_FILE_LOCALLY_MODIFIED : CACHED_FILE_FROM_SERVER;
352  *cache_file_path = GetCacheFilePath(resource_id, cache_entry.md5(),
353                                      file_origin);
354  return FILE_ERROR_OK;
355}
356
357void FileCache::StoreOnUIThread(const std::string& resource_id,
358                                const std::string& md5,
359                                const base::FilePath& source_path,
360                                FileOperationType file_operation_type,
361                                const FileOperationCallback& callback) {
362  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
363  DCHECK(!callback.is_null());
364
365  base::PostTaskAndReplyWithResult(blocking_task_runner_.get(),
366                                   FROM_HERE,
367                                   base::Bind(&FileCache::Store,
368                                              base::Unretained(this),
369                                              resource_id,
370                                              md5,
371                                              source_path,
372                                              file_operation_type),
373                                   callback);
374}
375
376FileError FileCache::Store(const std::string& resource_id,
377                           const std::string& md5,
378                           const base::FilePath& source_path,
379                           FileOperationType file_operation_type) {
380  AssertOnSequencedWorkerPool();
381  return StoreInternal(resource_id, md5, source_path, file_operation_type);
382}
383
384void FileCache::PinOnUIThread(const std::string& resource_id,
385                              const FileOperationCallback& callback) {
386  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
387  DCHECK(!callback.is_null());
388
389  base::PostTaskAndReplyWithResult(
390      blocking_task_runner_.get(),
391      FROM_HERE,
392      base::Bind(&FileCache::Pin, base::Unretained(this), resource_id),
393      callback);
394}
395
396FileError FileCache::Pin(const std::string& resource_id) {
397  AssertOnSequencedWorkerPool();
398
399  FileCacheEntry cache_entry;
400  storage_->GetCacheEntry(resource_id, &cache_entry);
401  cache_entry.set_is_pinned(true);
402  storage_->PutCacheEntry(resource_id, cache_entry);
403  return FILE_ERROR_OK;
404}
405
406void FileCache::UnpinOnUIThread(const std::string& resource_id,
407                                const FileOperationCallback& callback) {
408  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
409  DCHECK(!callback.is_null());
410
411  base::PostTaskAndReplyWithResult(
412      blocking_task_runner_.get(),
413      FROM_HERE,
414      base::Bind(&FileCache::Unpin, base::Unretained(this), resource_id),
415      callback);
416}
417
418FileError FileCache::Unpin(const std::string& resource_id) {
419  AssertOnSequencedWorkerPool();
420
421  // Unpinning a file means its entry must exist in cache.
422  FileCacheEntry cache_entry;
423  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
424    return FILE_ERROR_NOT_FOUND;
425
426  // Now that file operations have completed, update metadata.
427  if (cache_entry.is_present()) {
428    cache_entry.set_is_pinned(false);
429    storage_->PutCacheEntry(resource_id, cache_entry);
430  } else {
431    // Remove the existing entry if we are unpinning a non-present file.
432    storage_->RemoveCacheEntry(resource_id);
433  }
434
435  // Now it's a chance to free up space if needed.
436  FreeDiskSpaceIfNeededFor(0);
437
438  return FILE_ERROR_OK;
439}
440
441void FileCache::MarkAsMountedOnUIThread(
442    const std::string& resource_id,
443    const GetFileFromCacheCallback& callback) {
444  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
445  DCHECK(!callback.is_null());
446
447  base::FilePath* cache_file_path = new base::FilePath;
448  base::PostTaskAndReplyWithResult(
449      blocking_task_runner_.get(),
450      FROM_HERE,
451      base::Bind(&FileCache::MarkAsMounted,
452                 base::Unretained(this),
453                 resource_id,
454                 cache_file_path),
455      base::Bind(
456          RunGetFileFromCacheCallback, callback, base::Owned(cache_file_path)));
457}
458
459void FileCache::MarkAsUnmountedOnUIThread(
460    const base::FilePath& file_path,
461    const FileOperationCallback& callback) {
462  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
463  DCHECK(!callback.is_null());
464
465  base::PostTaskAndReplyWithResult(
466      blocking_task_runner_.get(),
467      FROM_HERE,
468      base::Bind(
469          &FileCache::MarkAsUnmounted, base::Unretained(this), file_path),
470      callback);
471}
472
473void FileCache::MarkDirtyOnUIThread(const std::string& resource_id,
474                                    const std::string& md5,
475                                    const FileOperationCallback& callback) {
476  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
477  DCHECK(!callback.is_null());
478
479  base::PostTaskAndReplyWithResult(
480      blocking_task_runner_.get(),
481      FROM_HERE,
482      base::Bind(
483          &FileCache::MarkDirty, base::Unretained(this), resource_id, md5),
484      callback);
485}
486
487FileError FileCache::MarkDirty(const std::string& resource_id,
488                               const std::string& md5) {
489  AssertOnSequencedWorkerPool();
490
491  // If file has already been marked dirty in previous instance of chrome, we
492  // would have lost the md5 info during cache initialization, because the file
493  // would have been renamed to .local extension.
494  // So, search for entry in cache without comparing md5.
495
496  // Marking a file dirty means its entry and actual file blob must exist in
497  // cache.
498  FileCacheEntry cache_entry;
499  if (!storage_->GetCacheEntry(resource_id, &cache_entry) ||
500      !cache_entry.is_present()) {
501    LOG(WARNING) << "Can't mark dirty a file that wasn't cached: res_id="
502                 << resource_id
503                 << ", md5=" << md5;
504    return FILE_ERROR_NOT_FOUND;
505  }
506
507  if (cache_entry.is_dirty())
508    return FILE_ERROR_OK;
509
510  // Get the current path of the file in cache.
511  base::FilePath source_path = GetCacheFilePath(resource_id, md5,
512                                                CACHED_FILE_FROM_SERVER);
513  // Determine destination path.
514  base::FilePath cache_file_path = GetCacheFilePath(
515      resource_id, md5, CACHED_FILE_LOCALLY_MODIFIED);
516
517  if (!MoveFile(source_path, cache_file_path))
518    return FILE_ERROR_FAILED;
519
520  // Now that file operations have completed, update metadata.
521  cache_entry.set_md5(md5);
522  cache_entry.set_is_dirty(true);
523  storage_->PutCacheEntry(resource_id, cache_entry);
524  return FILE_ERROR_OK;
525}
526
527FileError FileCache::ClearDirty(const std::string& resource_id,
528                                const std::string& md5) {
529  AssertOnSequencedWorkerPool();
530
531  // |md5| is the new .<md5> extension to rename the file to.
532  // So, search for entry in cache without comparing md5.
533  FileCacheEntry cache_entry;
534
535  // Clearing a dirty file means its entry and actual file blob must exist in
536  // cache.
537  if (!storage_->GetCacheEntry(resource_id, &cache_entry) ||
538      !cache_entry.is_present()) {
539    LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
540                 << "res_id=" << resource_id
541                 << ", md5=" << md5;
542    return FILE_ERROR_NOT_FOUND;
543  }
544
545  // If a file is not dirty (it should have been marked dirty via
546  // MarkDirtyInCache), clearing its dirty state is an invalid operation.
547  if (!cache_entry.is_dirty()) {
548    LOG(WARNING) << "Can't clear dirty state of a non-dirty file: res_id="
549                 << resource_id
550                 << ", md5=" << md5;
551    return FILE_ERROR_INVALID_OPERATION;
552  }
553
554  base::FilePath source_path = GetCacheFilePath(resource_id, md5,
555                                                CACHED_FILE_LOCALLY_MODIFIED);
556  base::FilePath dest_path = GetCacheFilePath(resource_id, md5,
557                                              CACHED_FILE_FROM_SERVER);
558  if (!MoveFile(source_path, dest_path))
559    return FILE_ERROR_FAILED;
560
561  // Now that file operations have completed, update metadata.
562  cache_entry.set_md5(md5);
563  cache_entry.set_is_dirty(false);
564  storage_->PutCacheEntry(resource_id, cache_entry);
565  return FILE_ERROR_OK;
566}
567
568void FileCache::RemoveOnUIThread(const std::string& resource_id,
569                                 const FileOperationCallback& callback) {
570  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
571  DCHECK(!callback.is_null());
572
573  base::PostTaskAndReplyWithResult(
574      blocking_task_runner_.get(),
575      FROM_HERE,
576      base::Bind(&FileCache::Remove, base::Unretained(this), resource_id),
577      callback);
578}
579
580FileError FileCache::Remove(const std::string& resource_id) {
581  AssertOnSequencedWorkerPool();
582
583  // MD5 is not passed into RemoveCacheEntry because we would delete all
584  // cache files corresponding to <resource_id> regardless of the md5.
585  // So, search for entry in cache without taking md5 into account.
586  FileCacheEntry cache_entry;
587
588  // If entry doesn't exist, nothing to do.
589  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
590    return FILE_ERROR_OK;
591
592  // Cannot delete a dirty or mounted file.
593  if (cache_entry.is_dirty() || mounted_files_.count(resource_id))
594    return FILE_ERROR_IN_USE;
595
596  // Delete files that match "<resource_id>.*" unless modified locally.
597  base::FilePath path_to_delete = GetCacheFilePath(resource_id, util::kWildCard,
598                                                   CACHED_FILE_FROM_SERVER);
599  base::FilePath path_to_keep = GetCacheFilePath(resource_id, std::string(),
600                                                 CACHED_FILE_LOCALLY_MODIFIED);
601  DeleteFilesSelectively(path_to_delete, path_to_keep);
602
603  // Now that all file operations have completed, remove from metadata.
604  storage_->RemoveCacheEntry(resource_id);
605
606  return FILE_ERROR_OK;
607}
608
609void FileCache::ClearAllOnUIThread(const InitializeCacheCallback& callback) {
610  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
611  DCHECK(!callback.is_null());
612
613  base::PostTaskAndReplyWithResult(
614      blocking_task_runner_.get(),
615      FROM_HERE,
616      base::Bind(&FileCache::ClearAll, base::Unretained(this)),
617      callback);
618}
619
620bool FileCache::Initialize() {
621  AssertOnSequencedWorkerPool();
622
623  if (!ImportOldDB(storage_->directory_path().Append(
624          kOldCacheMetadataDBName)) &&
625      !storage_->opened_existing_db()) {
626    CacheMap cache_map;
627    ScanCacheDirectory(cache_file_directory_, &cache_map);
628    for (CacheMap::const_iterator it = cache_map.begin();
629         it != cache_map.end(); ++it) {
630      storage_->PutCacheEntry(it->first, it->second);
631    }
632  }
633  return true;
634}
635
636void FileCache::Destroy() {
637  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
638
639  // Invalidate the weak pointer.
640  weak_ptr_factory_.InvalidateWeakPtrs();
641
642  // Destroy myself on the blocking pool.
643  // Note that base::DeletePointer<> cannot be used as the destructor of this
644  // class is private.
645  blocking_task_runner_->PostTask(
646      FROM_HERE,
647      base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this)));
648}
649
650void FileCache::DestroyOnBlockingPool() {
651  AssertOnSequencedWorkerPool();
652  delete this;
653}
654
655FileError FileCache::StoreInternal(const std::string& resource_id,
656                                   const std::string& md5,
657                                   const base::FilePath& source_path,
658                                   FileOperationType file_operation_type) {
659  AssertOnSequencedWorkerPool();
660
661  int64 file_size = 0;
662  if (file_operation_type == FILE_OPERATION_COPY) {
663    if (!file_util::GetFileSize(source_path, &file_size)) {
664      LOG(WARNING) << "Couldn't get file size for: " << source_path.value();
665      return FILE_ERROR_FAILED;
666    }
667  }
668  if (!FreeDiskSpaceIfNeededFor(file_size))
669    return FILE_ERROR_NO_SPACE;
670
671  FileCacheEntry cache_entry;
672  storage_->GetCacheEntry(resource_id, &cache_entry);
673
674  // If file is dirty or mounted, return error.
675  if (cache_entry.is_dirty() || mounted_files_.count(resource_id))
676    return FILE_ERROR_IN_USE;
677
678  base::FilePath dest_path = GetCacheFilePath(resource_id, md5,
679                                              CACHED_FILE_FROM_SERVER);
680  bool success = false;
681  switch (file_operation_type) {
682    case FILE_OPERATION_MOVE:
683      success = MoveFile(source_path, dest_path);
684      break;
685    case FILE_OPERATION_COPY:
686      success = CopyFile(source_path, dest_path);
687      break;
688    default:
689      NOTREACHED();
690  }
691
692  // Determine search pattern for stale filenames corresponding to resource_id,
693  // either "<resource_id>*" or "<resource_id>.*".
694  base::FilePath stale_filenames_pattern;
695  if (md5.empty()) {
696    // No md5 means no extension, append '*' after base name, i.e.
697    // "<resource_id>*".
698    // Cannot call |dest_path|.ReplaceExtension when there's no md5 extension:
699    // if base name of |dest_path| (i.e. escaped resource_id) contains the
700    // extension separator '.', ReplaceExtension will remove it and everything
701    // after it.  The result will be nothing like the escaped resource_id.
702    stale_filenames_pattern =
703        base::FilePath(dest_path.value() + util::kWildCard);
704  } else {
705    // Replace md5 extension with '*' i.e. "<resource_id>.*".
706    // Note that ReplaceExtension automatically prefixes the extension with the
707    // extension separator '.'.
708    stale_filenames_pattern = dest_path.ReplaceExtension(util::kWildCard);
709  }
710
711  // Delete files that match |stale_filenames_pattern| except for |dest_path|.
712  DeleteFilesSelectively(stale_filenames_pattern, dest_path);
713
714  if (success) {
715    // Now that file operations have completed, update metadata.
716    cache_entry.set_md5(md5);
717    cache_entry.set_is_present(true);
718    cache_entry.set_is_dirty(false);
719    storage_->PutCacheEntry(resource_id, cache_entry);
720  }
721
722  return success ? FILE_ERROR_OK : FILE_ERROR_FAILED;
723}
724
725FileError FileCache::MarkAsMounted(const std::string& resource_id,
726                                   base::FilePath* cache_file_path) {
727  AssertOnSequencedWorkerPool();
728  DCHECK(cache_file_path);
729
730  // Get cache entry associated with the resource_id and md5
731  FileCacheEntry cache_entry;
732  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
733    return FILE_ERROR_NOT_FOUND;
734
735  if (mounted_files_.count(resource_id))
736    return FILE_ERROR_INVALID_OPERATION;
737
738  // Ensure the file is readable to cros_disks. See crbug.com/236994.
739  base::FilePath path = GetCacheFilePath(
740      resource_id, cache_entry.md5(), CACHED_FILE_FROM_SERVER);
741  file_util::SetPosixFilePermissions(
742      path,
743      file_util::FILE_PERMISSION_READ_BY_USER |
744      file_util::FILE_PERMISSION_WRITE_BY_USER |
745      file_util::FILE_PERMISSION_READ_BY_GROUP |
746      file_util::FILE_PERMISSION_READ_BY_OTHERS);
747
748  mounted_files_.insert(resource_id);
749
750  *cache_file_path = path;
751  return FILE_ERROR_OK;
752}
753
754FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) {
755  AssertOnSequencedWorkerPool();
756  DCHECK(IsUnderFileCacheDirectory(file_path));
757
758  // Parse file path to obtain resource_id, md5 and extra_extension.
759  std::string resource_id;
760  std::string md5;
761  util::ParseCacheFilePath(file_path, &resource_id, &md5);
762
763  // Get cache entry associated with the resource_id and md5
764  FileCacheEntry cache_entry;
765  if (!GetCacheEntry(resource_id, md5, &cache_entry))
766    return FILE_ERROR_NOT_FOUND;
767
768  std::set<std::string>::iterator it = mounted_files_.find(resource_id);
769  if (it == mounted_files_.end())
770    return FILE_ERROR_INVALID_OPERATION;
771
772  mounted_files_.erase(it);
773  return FILE_ERROR_OK;
774}
775
776bool FileCache::ClearAll() {
777  AssertOnSequencedWorkerPool();
778
779  // Remove entries on the metadata.
780  scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
781      storage_->GetCacheEntryIterator();
782  for (; !it->IsAtEnd(); it->Advance())
783    storage_->RemoveCacheEntry(it->GetID());
784
785  if (it->HasError())
786    return false;
787
788  // Remove files.
789  base::FileEnumerator enumerator(cache_file_directory_,
790                                  false,  // not recursive
791                                  base::FileEnumerator::FILES);
792  for (base::FilePath file = enumerator.Next(); !file.empty();
793       file = enumerator.Next())
794    base::Delete(file, false /* recursive */);
795
796  return true;
797}
798
799bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
800                                  const base::FilePath& path) {
801  int64 free_space = 0;
802  if (free_disk_space_getter_)
803    free_space = free_disk_space_getter_->AmountOfFreeDiskSpace();
804  else
805    free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
806
807  // Subtract this as if this portion does not exist.
808  free_space -= kMinFreeSpace;
809  return (free_space >= num_bytes);
810}
811
812bool FileCache::ImportOldDB(const base::FilePath& old_db_path) {
813  if (!file_util::PathExists(old_db_path))  // Old DB is not there, do nothing.
814    return false;
815
816  // Copy all entries stored in the old DB.
817  bool imported = false;
818  {
819    FileCacheMetadata old_data(blocking_task_runner_);
820    if (old_data.Initialize(old_db_path) ==
821        FileCacheMetadata::INITIALIZE_OPENED) {
822      scoped_ptr<FileCacheMetadata::Iterator> it = old_data.GetIterator();
823      for (; !it->IsAtEnd(); it->Advance()) {
824        FileCacheEntry entry;
825        if (storage_->GetCacheEntry(it->GetKey(), &entry))
826          continue;  // Do not overwrite.
827
828        storage_->PutCacheEntry(it->GetKey(), it->GetValue());
829      }
830      imported = true;
831    }
832  }
833
834  // Delete old DB.
835  base::Delete(old_db_path, true /* recursive */ );
836  return imported;
837}
838
839}  // namespace internal
840}  // namespace drive
841