file_cache.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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/google_apis/task_util.h"
20#include "chromeos/chromeos_constants.h"
21#include "content/public/browser/browser_thread.h"
22
23using content::BrowserThread;
24
25namespace drive {
26namespace internal {
27namespace {
28
29typedef std::map<std::string, FileCacheEntry> CacheMap;
30
31// Returns true if |md5| matches the one in |cache_entry| with some
32// exceptions. See the function definition for details.
33bool CheckIfMd5Matches(const std::string& md5,
34                       const FileCacheEntry& cache_entry) {
35  if (cache_entry.is_dirty()) {
36    // If the entry is dirty, its MD5 may have been replaced by "local"
37    // during cache initialization, so we don't compare MD5.
38    return true;
39  } else if (cache_entry.is_pinned() && cache_entry.md5().empty()) {
40    // If the entry is pinned, it's ok for the entry to have an empty
41    // MD5. This can happen if the pinned file is not fetched.
42    return true;
43  } else if (md5.empty()) {
44    // If the MD5 matching is not requested, don't check MD5.
45    return true;
46  } else if (md5 == cache_entry.md5()) {
47    // Otherwise, compare the MD5.
48    return true;
49  }
50  return false;
51}
52
53// Scans cache subdirectory and insert found files to |cache_map|.
54void ScanCacheDirectory(const base::FilePath& directory_path,
55                        CacheMap* cache_map) {
56  base::FileEnumerator enumerator(directory_path,
57                                  false,  // not recursive
58                                  base::FileEnumerator::FILES);
59  for (base::FilePath current = enumerator.Next(); !current.empty();
60       current = enumerator.Next()) {
61    // Extract resource_id and md5 from filename.
62    std::string resource_id;
63    std::string md5;
64    util::ParseCacheFilePath(current, &resource_id, &md5);
65
66    // Determine cache state.
67    FileCacheEntry cache_entry;
68    cache_entry.set_md5(md5);
69    cache_entry.set_is_present(true);
70
71    // Add the dirty bit if |md5| indicates that the file is dirty.
72    if (md5 == util::kLocallyModifiedFileExtension)
73      cache_entry.set_is_dirty(true);
74
75    // Create and insert new entry into cache map.
76    cache_map->insert(std::make_pair(resource_id, cache_entry));
77  }
78}
79
80// Moves the file.
81bool MoveFile(const base::FilePath& source_path,
82              const base::FilePath& dest_path) {
83  if (!file_util::Move(source_path, dest_path)) {
84    LOG(ERROR) << "Failed to move " << source_path.value()
85               << " to " << dest_path.value();
86    return false;
87  }
88  DVLOG(1) << "Moved " << source_path.value() << " to " << dest_path.value();
89  return true;
90}
91
92// Copies the file.
93bool CopyFile(const base::FilePath& source_path,
94              const base::FilePath& dest_path) {
95  if (!file_util::CopyFile(source_path, dest_path)) {
96    LOG(ERROR) << "Failed to copy " << source_path.value()
97               << " to " << dest_path.value();
98    return false;
99  }
100  DVLOG(1) << "Copied " << source_path.value() << " to " << dest_path.value();
101  return true;
102}
103
104// Deletes all files that match |path_to_delete_pattern| except for
105// |path_to_keep| on blocking pool.
106// If |path_to_keep| is empty, all files in |path_to_delete_pattern| are
107// deleted.
108void DeleteFilesSelectively(const base::FilePath& path_to_delete_pattern,
109                            const base::FilePath& path_to_keep) {
110  // Enumerate all files in directory of |path_to_delete_pattern| that match
111  // base name of |path_to_delete_pattern|.
112  // If a file is not |path_to_keep|, delete it.
113  bool success = true;
114  base::FileEnumerator enumerator(
115      path_to_delete_pattern.DirName(),
116      false,  // not recursive
117      base::FileEnumerator::FILES,
118      path_to_delete_pattern.BaseName().value());
119  for (base::FilePath current = enumerator.Next(); !current.empty();
120       current = enumerator.Next()) {
121    // If |path_to_keep| is not empty and same as current, don't delete it.
122    if (!path_to_keep.empty() && current == path_to_keep)
123      continue;
124
125    success = file_util::Delete(current, false);
126    if (!success)
127      DVLOG(1) << "Error deleting " << current.value();
128    else
129      DVLOG(1) << "Deleted " << current.value();
130  }
131}
132
133// Runs callback with pointers dereferenced.
134// Used to implement GetFile, MarkAsMounted.
135void RunGetFileFromCacheCallback(
136    const GetFileFromCacheCallback& callback,
137    base::FilePath* file_path,
138    FileError error) {
139  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
140  DCHECK(!callback.is_null());
141  DCHECK(file_path);
142
143  callback.Run(error, *file_path);
144}
145
146// Runs callback with pointers dereferenced.
147// Used to implement GetCacheEntry().
148void RunGetCacheEntryCallback(const GetCacheEntryCallback& callback,
149                              FileCacheEntry* cache_entry,
150                              bool success) {
151  DCHECK(cache_entry);
152  DCHECK(!callback.is_null());
153  callback.Run(success, *cache_entry);
154}
155
156}  // namespace
157
158FileCache::FileCache(const base::FilePath& metadata_directory,
159                     const base::FilePath& cache_file_directory,
160                     base::SequencedTaskRunner* blocking_task_runner,
161                     FreeDiskSpaceGetterInterface* free_disk_space_getter)
162    : metadata_directory_(metadata_directory),
163      cache_file_directory_(cache_file_directory),
164      blocking_task_runner_(blocking_task_runner),
165      free_disk_space_getter_(free_disk_space_getter),
166      weak_ptr_factory_(this) {
167  DCHECK(blocking_task_runner_.get());
168  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
169}
170
171FileCache::~FileCache() {
172  // Must be on the sequenced worker pool, as |metadata_| must be deleted on
173  // the sequenced worker pool.
174  AssertOnSequencedWorkerPool();
175}
176
177base::FilePath FileCache::GetCacheFilePath(const std::string& resource_id,
178                                           const std::string& md5,
179                                           CachedFileOrigin file_origin) const {
180  // Runs on any thread.
181  // Filename is formatted as resource_id.md5, i.e. resource_id is the base
182  // name and md5 is the extension.
183  std::string base_name = util::EscapeCacheFileName(resource_id);
184  if (file_origin == CACHED_FILE_LOCALLY_MODIFIED) {
185    base_name += base::FilePath::kExtensionSeparator;
186    base_name += util::kLocallyModifiedFileExtension;
187  } else if (!md5.empty()) {
188    base_name += base::FilePath::kExtensionSeparator;
189    base_name += util::EscapeCacheFileName(md5);
190  }
191  return cache_file_directory_.Append(
192      base::FilePath::FromUTF8Unsafe(base_name));
193}
194
195void FileCache::AssertOnSequencedWorkerPool() {
196  DCHECK(!blocking_task_runner_.get() ||
197         blocking_task_runner_->RunsTasksOnCurrentThread());
198}
199
200bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
201  return cache_file_directory_.IsParent(path);
202}
203
204void FileCache::GetCacheEntryOnUIThread(const std::string& resource_id,
205                                        const std::string& md5,
206                                        const GetCacheEntryCallback& callback) {
207  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
208  DCHECK(!callback.is_null());
209
210  FileCacheEntry* cache_entry = new FileCacheEntry;
211  base::PostTaskAndReplyWithResult(
212      blocking_task_runner_.get(),
213      FROM_HERE,
214      base::Bind(&FileCache::GetCacheEntry,
215                 base::Unretained(this),
216                 resource_id,
217                 md5,
218                 cache_entry),
219      base::Bind(
220          &RunGetCacheEntryCallback, callback, base::Owned(cache_entry)));
221}
222
223bool FileCache::GetCacheEntry(const std::string& resource_id,
224                              const std::string& md5,
225                              FileCacheEntry* entry) {
226  DCHECK(entry);
227  AssertOnSequencedWorkerPool();
228  return metadata_->GetCacheEntry(resource_id, entry) &&
229      CheckIfMd5Matches(md5, *entry);
230}
231
232void FileCache::IterateOnUIThread(
233    const CacheIterateCallback& iteration_callback,
234    const base::Closure& completion_callback) {
235  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
236  DCHECK(!iteration_callback.is_null());
237  DCHECK(!completion_callback.is_null());
238
239  blocking_task_runner_->PostTaskAndReply(
240      FROM_HERE,
241      base::Bind(&FileCache::Iterate,
242                 base::Unretained(this),
243                 google_apis::CreateRelayCallback(iteration_callback)),
244      completion_callback);
245}
246
247void FileCache::Iterate(const CacheIterateCallback& iteration_callback) {
248  AssertOnSequencedWorkerPool();
249  DCHECK(!iteration_callback.is_null());
250
251  scoped_ptr<FileCacheMetadata::Iterator> it = metadata_->GetIterator();
252  for (; !it->IsAtEnd(); it->Advance())
253    iteration_callback.Run(it->GetKey(), it->GetValue());
254  DCHECK(!it->HasError());
255}
256
257void FileCache::FreeDiskSpaceIfNeededForOnUIThread(
258    int64 num_bytes,
259    const InitializeCacheCallback& callback) {
260  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
261  DCHECK(!callback.is_null());
262
263  base::PostTaskAndReplyWithResult(
264      blocking_task_runner_.get(),
265      FROM_HERE,
266      base::Bind(&FileCache::FreeDiskSpaceIfNeededFor,
267                 base::Unretained(this),
268                 num_bytes),
269      callback);
270}
271
272bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
273  AssertOnSequencedWorkerPool();
274
275  // Do nothing and return if we have enough space.
276  if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
277    return true;
278
279  // Otherwise, try to free up the disk space.
280  DVLOG(1) << "Freeing up disk space for " << num_bytes;
281
282  // Remove all entries unless specially marked.
283  scoped_ptr<FileCacheMetadata::Iterator> it = metadata_->GetIterator();
284  for (; !it->IsAtEnd(); it->Advance()) {
285    const FileCacheEntry& entry = it->GetValue();
286    if (!entry.is_pinned() &&
287        !entry.is_dirty() &&
288        !mounted_files_.count(it->GetKey()))
289      metadata_->RemoveCacheEntry(it->GetKey());
290  }
291  DCHECK(!it->HasError());
292
293  // Remove all files which have no corresponding cache entries.
294  base::FileEnumerator enumerator(cache_file_directory_,
295                                  false,  // not recursive
296                                  base::FileEnumerator::FILES);
297  std::string resource_id;
298  std::string md5;
299  FileCacheEntry entry;
300  for (base::FilePath current = enumerator.Next(); !current.empty();
301       current = enumerator.Next()) {
302    util::ParseCacheFilePath(current, &resource_id, &md5);
303    if (!GetCacheEntry(resource_id, md5, &entry))
304      file_util::Delete(current, false /* recursive */);
305  }
306
307  // Check the disk space again.
308  return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
309}
310
311void FileCache::GetFileOnUIThread(const std::string& resource_id,
312                                  const std::string& md5,
313                                  const GetFileFromCacheCallback& callback) {
314  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
315  DCHECK(!callback.is_null());
316
317  base::FilePath* cache_file_path = new base::FilePath;
318  base::PostTaskAndReplyWithResult(blocking_task_runner_.get(),
319                                   FROM_HERE,
320                                   base::Bind(&FileCache::GetFile,
321                                              base::Unretained(this),
322                                              resource_id,
323                                              md5,
324                                              cache_file_path),
325                                   base::Bind(&RunGetFileFromCacheCallback,
326                                              callback,
327                                              base::Owned(cache_file_path)));
328}
329
330FileError FileCache::GetFile(const std::string& resource_id,
331                             const std::string& md5,
332                             base::FilePath* cache_file_path) {
333  AssertOnSequencedWorkerPool();
334  DCHECK(cache_file_path);
335
336  FileCacheEntry cache_entry;
337  if (!GetCacheEntry(resource_id, md5, &cache_entry) ||
338      !cache_entry.is_present())
339    return FILE_ERROR_NOT_FOUND;
340
341  CachedFileOrigin file_origin = cache_entry.is_dirty() ?
342      CACHED_FILE_LOCALLY_MODIFIED : CACHED_FILE_FROM_SERVER;
343  *cache_file_path = GetCacheFilePath(resource_id, cache_entry.md5(),
344                                      file_origin);
345  return FILE_ERROR_OK;
346}
347
348void FileCache::StoreOnUIThread(const std::string& resource_id,
349                                const std::string& md5,
350                                const base::FilePath& source_path,
351                                FileOperationType file_operation_type,
352                                const FileOperationCallback& callback) {
353  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354  DCHECK(!callback.is_null());
355
356  base::PostTaskAndReplyWithResult(blocking_task_runner_.get(),
357                                   FROM_HERE,
358                                   base::Bind(&FileCache::Store,
359                                              base::Unretained(this),
360                                              resource_id,
361                                              md5,
362                                              source_path,
363                                              file_operation_type),
364                                   callback);
365}
366
367FileError FileCache::Store(const std::string& resource_id,
368                           const std::string& md5,
369                           const base::FilePath& source_path,
370                           FileOperationType file_operation_type) {
371  AssertOnSequencedWorkerPool();
372  return StoreInternal(resource_id, md5, source_path, file_operation_type);
373}
374
375void FileCache::PinOnUIThread(const std::string& resource_id,
376                              const FileOperationCallback& callback) {
377  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
378  DCHECK(!callback.is_null());
379
380  base::PostTaskAndReplyWithResult(
381      blocking_task_runner_.get(),
382      FROM_HERE,
383      base::Bind(&FileCache::Pin, base::Unretained(this), resource_id),
384      callback);
385}
386
387FileError FileCache::Pin(const std::string& resource_id) {
388  AssertOnSequencedWorkerPool();
389
390  FileCacheEntry cache_entry;
391  metadata_->GetCacheEntry(resource_id, &cache_entry);
392  cache_entry.set_is_pinned(true);
393  metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry);
394  return FILE_ERROR_OK;
395}
396
397void FileCache::UnpinOnUIThread(const std::string& resource_id,
398                                const FileOperationCallback& callback) {
399  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
400  DCHECK(!callback.is_null());
401
402  base::PostTaskAndReplyWithResult(
403      blocking_task_runner_.get(),
404      FROM_HERE,
405      base::Bind(&FileCache::Unpin, base::Unretained(this), resource_id),
406      callback);
407}
408
409FileError FileCache::Unpin(const std::string& resource_id) {
410  AssertOnSequencedWorkerPool();
411
412  // Unpinning a file means its entry must exist in cache.
413  FileCacheEntry cache_entry;
414  if (!metadata_->GetCacheEntry(resource_id, &cache_entry))
415    return FILE_ERROR_NOT_FOUND;
416
417  // Now that file operations have completed, update metadata.
418  if (cache_entry.is_present()) {
419    cache_entry.set_is_pinned(false);
420    metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry);
421  } else {
422    // Remove the existing entry if we are unpinning a non-present file.
423    metadata_->RemoveCacheEntry(resource_id);
424  }
425
426  // Now it's a chance to free up space if needed.
427  FreeDiskSpaceIfNeededFor(0);
428
429  return FILE_ERROR_OK;
430}
431
432void FileCache::MarkAsMountedOnUIThread(
433    const std::string& resource_id,
434    const GetFileFromCacheCallback& callback) {
435  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
436  DCHECK(!callback.is_null());
437
438  base::FilePath* cache_file_path = new base::FilePath;
439  base::PostTaskAndReplyWithResult(
440      blocking_task_runner_.get(),
441      FROM_HERE,
442      base::Bind(&FileCache::MarkAsMounted,
443                 base::Unretained(this),
444                 resource_id,
445                 cache_file_path),
446      base::Bind(
447          RunGetFileFromCacheCallback, callback, base::Owned(cache_file_path)));
448}
449
450void FileCache::MarkAsUnmountedOnUIThread(
451    const base::FilePath& file_path,
452    const FileOperationCallback& callback) {
453  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
454  DCHECK(!callback.is_null());
455
456  base::PostTaskAndReplyWithResult(
457      blocking_task_runner_.get(),
458      FROM_HERE,
459      base::Bind(
460          &FileCache::MarkAsUnmounted, base::Unretained(this), file_path),
461      callback);
462}
463
464void FileCache::MarkDirtyOnUIThread(const std::string& resource_id,
465                                    const std::string& md5,
466                                    const FileOperationCallback& callback) {
467  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
468  DCHECK(!callback.is_null());
469
470  base::PostTaskAndReplyWithResult(
471      blocking_task_runner_.get(),
472      FROM_HERE,
473      base::Bind(
474          &FileCache::MarkDirty, base::Unretained(this), resource_id, md5),
475      callback);
476}
477
478FileError FileCache::MarkDirty(const std::string& resource_id,
479                               const std::string& md5) {
480  AssertOnSequencedWorkerPool();
481
482  // If file has already been marked dirty in previous instance of chrome, we
483  // would have lost the md5 info during cache initialization, because the file
484  // would have been renamed to .local extension.
485  // So, search for entry in cache without comparing md5.
486
487  // Marking a file dirty means its entry and actual file blob must exist in
488  // cache.
489  FileCacheEntry cache_entry;
490  if (!metadata_->GetCacheEntry(resource_id, &cache_entry) ||
491      !cache_entry.is_present()) {
492    LOG(WARNING) << "Can't mark dirty a file that wasn't cached: res_id="
493                 << resource_id
494                 << ", md5=" << md5;
495    return FILE_ERROR_NOT_FOUND;
496  }
497
498  if (cache_entry.is_dirty())
499    return FILE_ERROR_OK;
500
501  // Get the current path of the file in cache.
502  base::FilePath source_path = GetCacheFilePath(resource_id, md5,
503                                                CACHED_FILE_FROM_SERVER);
504  // Determine destination path.
505  base::FilePath cache_file_path = GetCacheFilePath(
506      resource_id, md5, CACHED_FILE_LOCALLY_MODIFIED);
507
508  if (!MoveFile(source_path, cache_file_path))
509    return FILE_ERROR_FAILED;
510
511  // Now that file operations have completed, update metadata.
512  cache_entry.set_md5(md5);
513  cache_entry.set_is_dirty(true);
514  metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry);
515  return FILE_ERROR_OK;
516}
517
518FileError FileCache::ClearDirty(const std::string& resource_id,
519                                const std::string& md5) {
520  AssertOnSequencedWorkerPool();
521
522  // |md5| is the new .<md5> extension to rename the file to.
523  // So, search for entry in cache without comparing md5.
524  FileCacheEntry cache_entry;
525
526  // Clearing a dirty file means its entry and actual file blob must exist in
527  // cache.
528  if (!metadata_->GetCacheEntry(resource_id, &cache_entry) ||
529      !cache_entry.is_present()) {
530    LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
531                 << "res_id=" << resource_id
532                 << ", md5=" << md5;
533    return FILE_ERROR_NOT_FOUND;
534  }
535
536  // If a file is not dirty (it should have been marked dirty via
537  // MarkDirtyInCache), clearing its dirty state is an invalid operation.
538  if (!cache_entry.is_dirty()) {
539    LOG(WARNING) << "Can't clear dirty state of a non-dirty file: res_id="
540                 << resource_id
541                 << ", md5=" << md5;
542    return FILE_ERROR_INVALID_OPERATION;
543  }
544
545  base::FilePath source_path = GetCacheFilePath(resource_id, md5,
546                                                CACHED_FILE_LOCALLY_MODIFIED);
547  base::FilePath dest_path = GetCacheFilePath(resource_id, md5,
548                                              CACHED_FILE_FROM_SERVER);
549  if (!MoveFile(source_path, dest_path))
550    return FILE_ERROR_FAILED;
551
552  // Now that file operations have completed, update metadata.
553  cache_entry.set_md5(md5);
554  cache_entry.set_is_dirty(false);
555  metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry);
556  return FILE_ERROR_OK;
557}
558
559void FileCache::RemoveOnUIThread(const std::string& resource_id,
560                                 const FileOperationCallback& callback) {
561  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
562  DCHECK(!callback.is_null());
563
564  base::PostTaskAndReplyWithResult(
565      blocking_task_runner_.get(),
566      FROM_HERE,
567      base::Bind(&FileCache::Remove, base::Unretained(this), resource_id),
568      callback);
569}
570
571FileError FileCache::Remove(const std::string& resource_id) {
572  AssertOnSequencedWorkerPool();
573
574  // MD5 is not passed into RemoveCacheEntry because we would delete all
575  // cache files corresponding to <resource_id> regardless of the md5.
576  // So, search for entry in cache without taking md5 into account.
577  FileCacheEntry cache_entry;
578
579  // If entry doesn't exist, nothing to do.
580  if (!metadata_->GetCacheEntry(resource_id, &cache_entry))
581    return FILE_ERROR_OK;
582
583  // Cannot delete a dirty or mounted file.
584  if (cache_entry.is_dirty() || mounted_files_.count(resource_id))
585    return FILE_ERROR_IN_USE;
586
587  // Delete files that match "<resource_id>.*" unless modified locally.
588  base::FilePath path_to_delete = GetCacheFilePath(resource_id, util::kWildCard,
589                                                   CACHED_FILE_FROM_SERVER);
590  base::FilePath path_to_keep = GetCacheFilePath(resource_id, std::string(),
591                                                 CACHED_FILE_LOCALLY_MODIFIED);
592  DeleteFilesSelectively(path_to_delete, path_to_keep);
593
594  // Now that all file operations have completed, remove from metadata.
595  metadata_->RemoveCacheEntry(resource_id);
596
597  return FILE_ERROR_OK;
598}
599
600void FileCache::ClearAllOnUIThread(const InitializeCacheCallback& callback) {
601  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
602  DCHECK(!callback.is_null());
603
604  base::PostTaskAndReplyWithResult(
605      blocking_task_runner_.get(),
606      FROM_HERE,
607      base::Bind(&FileCache::ClearAll, base::Unretained(this)),
608      callback);
609}
610
611bool FileCache::Initialize() {
612  AssertOnSequencedWorkerPool();
613
614  metadata_.reset(new FileCacheMetadata(blocking_task_runner_.get()));
615
616  switch (metadata_->Initialize(metadata_directory_)) {
617    case FileCacheMetadata::INITIALIZE_FAILED:
618      return false;
619
620    case FileCacheMetadata::INITIALIZE_OPENED:  // Do nothing.
621      break;
622
623    case FileCacheMetadata::INITIALIZE_CREATED: {
624      CacheMap cache_map;
625      ScanCacheDirectory(cache_file_directory_, &cache_map);
626      for (CacheMap::const_iterator it = cache_map.begin();
627           it != cache_map.end(); ++it) {
628        metadata_->AddOrUpdateCacheEntry(it->first, it->second);
629      }
630      break;
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  metadata_->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    metadata_->AddOrUpdateCacheEntry(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 (!metadata_->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<FileCacheMetadata::Iterator> it = metadata_->GetIterator();
781  for (; !it->IsAtEnd(); it->Advance())
782    metadata_->RemoveCacheEntry(it->GetKey());
783
784  if (it->HasError())
785    return false;
786
787  // Remove files.
788  base::FileEnumerator enumerator(cache_file_directory_,
789                                  false,  // not recursive
790                                  base::FileEnumerator::FILES);
791  for (base::FilePath file = enumerator.Next(); !file.empty();
792       file = enumerator.Next())
793    file_util::Delete(file, false /* recursive */);
794
795  return true;
796}
797
798bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
799                                  const base::FilePath& path) {
800  int64 free_space = 0;
801  if (free_disk_space_getter_)
802    free_space = free_disk_space_getter_->AmountOfFreeDiskSpace();
803  else
804    free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
805
806  // Subtract this as if this portion does not exist.
807  free_space -= kMinFreeSpace;
808  return (free_space >= num_bytes);
809}
810
811}  // namespace internal
812}  // namespace drive
813