file_cache.cc revision bbcdd45c55eb7c4641ab97aef9889b0fc828e7d3
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// Returns resource ID extracted from the path.
55std::string GetResourceIdFromPath(const base::FilePath& path) {
56  return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
57}
58
59// Scans cache subdirectory and insert found files to |cache_map|.
60void ScanCacheDirectory(const base::FilePath& directory_path,
61                        CacheMap* cache_map) {
62  base::FileEnumerator enumerator(directory_path,
63                                  false,  // not recursive
64                                  base::FileEnumerator::FILES);
65  for (base::FilePath current = enumerator.Next(); !current.empty();
66       current = enumerator.Next()) {
67    std::string resource_id = GetResourceIdFromPath(current);
68
69    // Calculate MD5.
70    std::string md5 = util::GetMd5Digest(current);
71    if (md5.empty())
72      continue;
73
74    // Determine cache state.
75    FileCacheEntry cache_entry;
76    cache_entry.set_md5(md5);
77    cache_entry.set_is_present(true);
78
79    // Create and insert new entry into cache map.
80    cache_map->insert(std::make_pair(resource_id, cache_entry));
81  }
82}
83
84// Runs callback with pointers dereferenced.
85// Used to implement GetFile, MarkAsMounted.
86void RunGetFileFromCacheCallback(const GetFileFromCacheCallback& callback,
87                                 base::FilePath* file_path,
88                                 FileError error) {
89  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
90  DCHECK(!callback.is_null());
91  DCHECK(file_path);
92
93  callback.Run(error, *file_path);
94}
95
96// Runs callback with pointers dereferenced.
97// Used to implement GetCacheEntry().
98void RunGetCacheEntryCallback(const GetCacheEntryCallback& callback,
99                              FileCacheEntry* cache_entry,
100                              bool success) {
101  DCHECK(cache_entry);
102  DCHECK(!callback.is_null());
103  callback.Run(success, *cache_entry);
104}
105
106// Calls |iteration_callback| with each entry in |cache|.
107void IterateCache(FileCache* cache,
108                  const CacheIterateCallback& iteration_callback) {
109  scoped_ptr<FileCache::Iterator> it = cache->GetIterator();
110  for (; !it->IsAtEnd(); it->Advance())
111    iteration_callback.Run(it->GetID(), it->GetValue());
112  DCHECK(!it->HasError());
113}
114
115}  // namespace
116
117const base::FilePath::CharType FileCache::kOldCacheMetadataDBName[] =
118    FILE_PATH_LITERAL("cache_metadata.db");
119
120FileCache::FileCache(ResourceMetadataStorage* storage,
121                     const base::FilePath& cache_file_directory,
122                     base::SequencedTaskRunner* blocking_task_runner,
123                     FreeDiskSpaceGetterInterface* free_disk_space_getter)
124    : cache_file_directory_(cache_file_directory),
125      blocking_task_runner_(blocking_task_runner),
126      storage_(storage),
127      free_disk_space_getter_(free_disk_space_getter),
128      weak_ptr_factory_(this) {
129  DCHECK(blocking_task_runner_.get());
130  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
131}
132
133FileCache::~FileCache() {
134  // Must be on the sequenced worker pool, as |metadata_| must be deleted on
135  // the sequenced worker pool.
136  AssertOnSequencedWorkerPool();
137}
138
139base::FilePath FileCache::GetCacheFilePath(
140    const std::string& resource_id) const {
141  return cache_file_directory_.Append(
142      base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(resource_id)));
143}
144
145void FileCache::AssertOnSequencedWorkerPool() {
146  DCHECK(!blocking_task_runner_.get() ||
147         blocking_task_runner_->RunsTasksOnCurrentThread());
148}
149
150bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
151  return cache_file_directory_.IsParent(path);
152}
153
154void FileCache::GetCacheEntryOnUIThread(const std::string& resource_id,
155                                        const std::string& md5,
156                                        const GetCacheEntryCallback& callback) {
157  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
158  DCHECK(!callback.is_null());
159
160  FileCacheEntry* cache_entry = new FileCacheEntry;
161  base::PostTaskAndReplyWithResult(
162      blocking_task_runner_.get(),
163      FROM_HERE,
164      base::Bind(&FileCache::GetCacheEntry,
165                 base::Unretained(this),
166                 resource_id,
167                 md5,
168                 cache_entry),
169      base::Bind(
170          &RunGetCacheEntryCallback, callback, base::Owned(cache_entry)));
171}
172
173bool FileCache::GetCacheEntry(const std::string& resource_id,
174                              const std::string& md5,
175                              FileCacheEntry* entry) {
176  DCHECK(entry);
177  AssertOnSequencedWorkerPool();
178  return storage_->GetCacheEntry(resource_id, entry) &&
179      CheckIfMd5Matches(md5, *entry);
180}
181
182void FileCache::IterateOnUIThread(
183    const CacheIterateCallback& iteration_callback,
184    const base::Closure& completion_callback) {
185  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
186  DCHECK(!iteration_callback.is_null());
187  DCHECK(!completion_callback.is_null());
188
189  blocking_task_runner_->PostTaskAndReply(
190      FROM_HERE,
191      base::Bind(&IterateCache,
192                 base::Unretained(this),
193                 google_apis::CreateRelayCallback(iteration_callback)),
194      completion_callback);
195}
196
197scoped_ptr<FileCache::Iterator> FileCache::GetIterator() {
198  AssertOnSequencedWorkerPool();
199  return storage_->GetCacheEntryIterator();
200}
201
202bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
203  AssertOnSequencedWorkerPool();
204
205  // Do nothing and return if we have enough space.
206  if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
207    return true;
208
209  // Otherwise, try to free up the disk space.
210  DVLOG(1) << "Freeing up disk space for " << num_bytes;
211
212  // Remove all entries unless specially marked.
213  scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
214      storage_->GetCacheEntryIterator();
215  for (; !it->IsAtEnd(); it->Advance()) {
216    const FileCacheEntry& entry = it->GetValue();
217    if (!entry.is_pinned() &&
218        !entry.is_dirty() &&
219        !mounted_files_.count(it->GetID()))
220      storage_->RemoveCacheEntry(it->GetID());
221  }
222  DCHECK(!it->HasError());
223
224  // Remove all files which have no corresponding cache entries.
225  base::FileEnumerator enumerator(cache_file_directory_,
226                                  false,  // not recursive
227                                  base::FileEnumerator::FILES);
228  FileCacheEntry entry;
229  for (base::FilePath current = enumerator.Next(); !current.empty();
230       current = enumerator.Next()) {
231    std::string resource_id = GetResourceIdFromPath(current);
232    if (!storage_->GetCacheEntry(resource_id, &entry))
233      base::DeleteFile(current, false /* recursive */);
234  }
235
236  // Check the disk space again.
237  return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
238}
239
240void FileCache::GetFileOnUIThread(const std::string& resource_id,
241                                  const std::string& md5,
242                                  const GetFileFromCacheCallback& callback) {
243  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244  DCHECK(!callback.is_null());
245
246  base::FilePath* cache_file_path = new base::FilePath;
247  base::PostTaskAndReplyWithResult(blocking_task_runner_.get(),
248                                   FROM_HERE,
249                                   base::Bind(&FileCache::GetFile,
250                                              base::Unretained(this),
251                                              resource_id,
252                                              md5,
253                                              cache_file_path),
254                                   base::Bind(&RunGetFileFromCacheCallback,
255                                              callback,
256                                              base::Owned(cache_file_path)));
257}
258
259FileError FileCache::GetFile(const std::string& resource_id,
260                             const std::string& md5,
261                             base::FilePath* cache_file_path) {
262  AssertOnSequencedWorkerPool();
263  DCHECK(cache_file_path);
264
265  FileCacheEntry cache_entry;
266  if (!GetCacheEntry(resource_id, md5, &cache_entry) ||
267      !cache_entry.is_present())
268    return FILE_ERROR_NOT_FOUND;
269
270  *cache_file_path = GetCacheFilePath(resource_id);
271  return FILE_ERROR_OK;
272}
273
274void FileCache::StoreOnUIThread(const std::string& resource_id,
275                                const std::string& md5,
276                                const base::FilePath& source_path,
277                                FileOperationType file_operation_type,
278                                const FileOperationCallback& callback) {
279  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
280  DCHECK(!callback.is_null());
281
282  base::PostTaskAndReplyWithResult(blocking_task_runner_.get(),
283                                   FROM_HERE,
284                                   base::Bind(&FileCache::Store,
285                                              base::Unretained(this),
286                                              resource_id,
287                                              md5,
288                                              source_path,
289                                              file_operation_type),
290                                   callback);
291}
292
293FileError FileCache::Store(const std::string& resource_id,
294                           const std::string& md5,
295                           const base::FilePath& source_path,
296                           FileOperationType file_operation_type) {
297  AssertOnSequencedWorkerPool();
298  return StoreInternal(resource_id, md5, source_path, file_operation_type);
299}
300
301void FileCache::PinOnUIThread(const std::string& resource_id,
302                              const FileOperationCallback& callback) {
303  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
304  DCHECK(!callback.is_null());
305
306  base::PostTaskAndReplyWithResult(
307      blocking_task_runner_.get(),
308      FROM_HERE,
309      base::Bind(&FileCache::Pin, base::Unretained(this), resource_id),
310      callback);
311}
312
313FileError FileCache::Pin(const std::string& resource_id) {
314  AssertOnSequencedWorkerPool();
315
316  FileCacheEntry cache_entry;
317  storage_->GetCacheEntry(resource_id, &cache_entry);
318  cache_entry.set_is_pinned(true);
319  return storage_->PutCacheEntry(resource_id, cache_entry) ?
320      FILE_ERROR_OK : FILE_ERROR_FAILED;
321}
322
323void FileCache::UnpinOnUIThread(const std::string& resource_id,
324                                const FileOperationCallback& callback) {
325  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
326  DCHECK(!callback.is_null());
327
328  base::PostTaskAndReplyWithResult(
329      blocking_task_runner_.get(),
330      FROM_HERE,
331      base::Bind(&FileCache::Unpin, base::Unretained(this), resource_id),
332      callback);
333}
334
335FileError FileCache::Unpin(const std::string& resource_id) {
336  AssertOnSequencedWorkerPool();
337
338  // Unpinning a file means its entry must exist in cache.
339  FileCacheEntry cache_entry;
340  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
341    return FILE_ERROR_NOT_FOUND;
342
343  // Now that file operations have completed, update metadata.
344  if (cache_entry.is_present()) {
345    cache_entry.set_is_pinned(false);
346    if (!storage_->PutCacheEntry(resource_id, cache_entry))
347      return FILE_ERROR_FAILED;
348  } else {
349    // Remove the existing entry if we are unpinning a non-present file.
350    if  (!storage_->RemoveCacheEntry(resource_id))
351      return FILE_ERROR_FAILED;
352  }
353
354  // Now it's a chance to free up space if needed.
355  FreeDiskSpaceIfNeededFor(0);
356
357  return FILE_ERROR_OK;
358}
359
360void FileCache::MarkAsMountedOnUIThread(
361    const std::string& resource_id,
362    const GetFileFromCacheCallback& callback) {
363  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
364  DCHECK(!callback.is_null());
365
366  base::FilePath* cache_file_path = new base::FilePath;
367  base::PostTaskAndReplyWithResult(
368      blocking_task_runner_.get(),
369      FROM_HERE,
370      base::Bind(&FileCache::MarkAsMounted,
371                 base::Unretained(this),
372                 resource_id,
373                 cache_file_path),
374      base::Bind(
375          RunGetFileFromCacheCallback, callback, base::Owned(cache_file_path)));
376}
377
378void FileCache::MarkAsUnmountedOnUIThread(
379    const base::FilePath& file_path,
380    const FileOperationCallback& callback) {
381  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
382  DCHECK(!callback.is_null());
383
384  base::PostTaskAndReplyWithResult(
385      blocking_task_runner_.get(),
386      FROM_HERE,
387      base::Bind(
388          &FileCache::MarkAsUnmounted, base::Unretained(this), file_path),
389      callback);
390}
391
392void FileCache::MarkDirtyOnUIThread(const std::string& resource_id,
393                                    const FileOperationCallback& callback) {
394  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395  DCHECK(!callback.is_null());
396
397  base::PostTaskAndReplyWithResult(
398      blocking_task_runner_.get(),
399      FROM_HERE,
400      base::Bind(&FileCache::MarkDirty, base::Unretained(this), resource_id),
401      callback);
402}
403
404FileError FileCache::MarkDirty(const std::string& resource_id) {
405  AssertOnSequencedWorkerPool();
406
407  // Marking a file dirty means its entry and actual file blob must exist in
408  // cache.
409  FileCacheEntry cache_entry;
410  if (!storage_->GetCacheEntry(resource_id, &cache_entry) ||
411      !cache_entry.is_present()) {
412    LOG(WARNING) << "Can't mark dirty a file that wasn't cached: "
413                 << resource_id;
414    return FILE_ERROR_NOT_FOUND;
415  }
416
417  if (cache_entry.is_dirty())
418    return FILE_ERROR_OK;
419
420  // Now that file operations have completed, update metadata.
421  cache_entry.set_is_dirty(true);
422  return storage_->PutCacheEntry(resource_id, cache_entry) ?
423      FILE_ERROR_OK : FILE_ERROR_FAILED;
424}
425
426FileError FileCache::ClearDirty(const std::string& resource_id,
427                                const std::string& md5) {
428  AssertOnSequencedWorkerPool();
429
430  // |md5| is the new .<md5> extension to rename the file to.
431  // So, search for entry in cache without comparing md5.
432  FileCacheEntry cache_entry;
433
434  // Clearing a dirty file means its entry and actual file blob must exist in
435  // cache.
436  if (!storage_->GetCacheEntry(resource_id, &cache_entry) ||
437      !cache_entry.is_present()) {
438    LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
439                 << "res_id=" << resource_id
440                 << ", md5=" << md5;
441    return FILE_ERROR_NOT_FOUND;
442  }
443
444  // If a file is not dirty (it should have been marked dirty via
445  // MarkDirtyInCache), clearing its dirty state is an invalid operation.
446  if (!cache_entry.is_dirty()) {
447    LOG(WARNING) << "Can't clear dirty state of a non-dirty file: res_id="
448                 << resource_id
449                 << ", md5=" << md5;
450    return FILE_ERROR_INVALID_OPERATION;
451  }
452
453  // Now that file operations have completed, update metadata.
454  cache_entry.set_md5(md5);
455  cache_entry.set_is_dirty(false);
456  return storage_->PutCacheEntry(resource_id, cache_entry) ?
457      FILE_ERROR_OK : FILE_ERROR_FAILED;
458}
459
460void FileCache::RemoveOnUIThread(const std::string& resource_id,
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(&FileCache::Remove, base::Unretained(this), resource_id),
469      callback);
470}
471
472FileError FileCache::Remove(const std::string& resource_id) {
473  AssertOnSequencedWorkerPool();
474
475  FileCacheEntry cache_entry;
476
477  // If entry doesn't exist, nothing to do.
478  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
479    return FILE_ERROR_OK;
480
481  // Cannot delete a mounted file.
482  if (mounted_files_.count(resource_id))
483    return FILE_ERROR_IN_USE;
484
485  // Delete the file.
486  base::FilePath path = GetCacheFilePath(resource_id);
487  if (!base::DeleteFile(path, false /* recursive */))
488    return FILE_ERROR_FAILED;
489
490  // Now that all file operations have completed, remove from metadata.
491  return storage_->RemoveCacheEntry(resource_id) ?
492      FILE_ERROR_OK : FILE_ERROR_FAILED;
493}
494
495void FileCache::ClearAllOnUIThread(const ClearAllCallback& callback) {
496  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
497  DCHECK(!callback.is_null());
498
499  base::PostTaskAndReplyWithResult(
500      blocking_task_runner_.get(),
501      FROM_HERE,
502      base::Bind(&FileCache::ClearAll, base::Unretained(this)),
503      callback);
504}
505
506bool FileCache::Initialize() {
507  AssertOnSequencedWorkerPool();
508
509  RenameCacheFilesToNewFormat();
510
511  if (!ImportOldDB(storage_->directory_path().Append(
512          kOldCacheMetadataDBName)) &&
513      !storage_->opened_existing_db()) {
514    CacheMap cache_map;
515    ScanCacheDirectory(cache_file_directory_, &cache_map);
516    for (CacheMap::const_iterator it = cache_map.begin();
517         it != cache_map.end(); ++it) {
518      storage_->PutCacheEntry(it->first, it->second);
519    }
520  }
521  return true;
522}
523
524void FileCache::Destroy() {
525  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
526
527  // Invalidate the weak pointer.
528  weak_ptr_factory_.InvalidateWeakPtrs();
529
530  // Destroy myself on the blocking pool.
531  // Note that base::DeletePointer<> cannot be used as the destructor of this
532  // class is private.
533  blocking_task_runner_->PostTask(
534      FROM_HERE,
535      base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this)));
536}
537
538void FileCache::DestroyOnBlockingPool() {
539  AssertOnSequencedWorkerPool();
540  delete this;
541}
542
543FileError FileCache::StoreInternal(const std::string& resource_id,
544                                   const std::string& md5,
545                                   const base::FilePath& source_path,
546                                   FileOperationType file_operation_type) {
547  AssertOnSequencedWorkerPool();
548
549  int64 file_size = 0;
550  if (file_operation_type == FILE_OPERATION_COPY) {
551    if (!file_util::GetFileSize(source_path, &file_size)) {
552      LOG(WARNING) << "Couldn't get file size for: " << source_path.value();
553      return FILE_ERROR_FAILED;
554    }
555  }
556  if (!FreeDiskSpaceIfNeededFor(file_size))
557    return FILE_ERROR_NO_SPACE;
558
559  FileCacheEntry cache_entry;
560  storage_->GetCacheEntry(resource_id, &cache_entry);
561
562  // If file is dirty or mounted, return error.
563  if (cache_entry.is_dirty() || mounted_files_.count(resource_id))
564    return FILE_ERROR_IN_USE;
565
566  base::FilePath dest_path = GetCacheFilePath(resource_id);
567  bool success = false;
568  switch (file_operation_type) {
569    case FILE_OPERATION_MOVE:
570      success = base::Move(source_path, dest_path);
571      break;
572    case FILE_OPERATION_COPY:
573      success = base::CopyFile(source_path, dest_path);
574      break;
575    default:
576      NOTREACHED();
577  }
578
579  if (!success) {
580    LOG(ERROR) << "Failed to store: "
581               << "source_path = " << source_path.value() << ", "
582               << "dest_path = " << dest_path.value() << ", "
583               << "file_operation_type = " << file_operation_type;
584    return FILE_ERROR_FAILED;
585  }
586
587  // Now that file operations have completed, update metadata.
588  cache_entry.set_md5(md5);
589  cache_entry.set_is_present(true);
590  cache_entry.set_is_dirty(false);
591  return storage_->PutCacheEntry(resource_id, cache_entry) ?
592      FILE_ERROR_OK : FILE_ERROR_FAILED;
593}
594
595FileError FileCache::MarkAsMounted(const std::string& resource_id,
596                                   base::FilePath* cache_file_path) {
597  AssertOnSequencedWorkerPool();
598  DCHECK(cache_file_path);
599
600  // Get cache entry associated with the resource_id and md5
601  FileCacheEntry cache_entry;
602  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
603    return FILE_ERROR_NOT_FOUND;
604
605  if (mounted_files_.count(resource_id))
606    return FILE_ERROR_INVALID_OPERATION;
607
608  // Ensure the file is readable to cros_disks. See crbug.com/236994.
609  base::FilePath path = GetCacheFilePath(resource_id);
610  if (!file_util::SetPosixFilePermissions(
611          path,
612          file_util::FILE_PERMISSION_READ_BY_USER |
613          file_util::FILE_PERMISSION_WRITE_BY_USER |
614          file_util::FILE_PERMISSION_READ_BY_GROUP |
615          file_util::FILE_PERMISSION_READ_BY_OTHERS))
616    return FILE_ERROR_FAILED;
617
618  mounted_files_.insert(resource_id);
619
620  *cache_file_path = path;
621  return FILE_ERROR_OK;
622}
623
624FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) {
625  AssertOnSequencedWorkerPool();
626  DCHECK(IsUnderFileCacheDirectory(file_path));
627
628  std::string resource_id = GetResourceIdFromPath(file_path);
629
630  // Get cache entry associated with the resource_id and md5
631  FileCacheEntry cache_entry;
632  if (!storage_->GetCacheEntry(resource_id, &cache_entry))
633    return FILE_ERROR_NOT_FOUND;
634
635  std::set<std::string>::iterator it = mounted_files_.find(resource_id);
636  if (it == mounted_files_.end())
637    return FILE_ERROR_INVALID_OPERATION;
638
639  mounted_files_.erase(it);
640  return FILE_ERROR_OK;
641}
642
643bool FileCache::ClearAll() {
644  AssertOnSequencedWorkerPool();
645
646  // Remove entries on the metadata.
647  scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
648      storage_->GetCacheEntryIterator();
649  for (; !it->IsAtEnd(); it->Advance()) {
650    if (!storage_->RemoveCacheEntry(it->GetID()))
651      return false;
652  }
653
654  if (it->HasError())
655    return false;
656
657  // Remove files.
658  base::FileEnumerator enumerator(cache_file_directory_,
659                                  false,  // not recursive
660                                  base::FileEnumerator::FILES);
661  for (base::FilePath file = enumerator.Next(); !file.empty();
662       file = enumerator.Next())
663    base::DeleteFile(file, false /* recursive */);
664
665  return true;
666}
667
668bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
669                                  const base::FilePath& path) {
670  int64 free_space = 0;
671  if (free_disk_space_getter_)
672    free_space = free_disk_space_getter_->AmountOfFreeDiskSpace();
673  else
674    free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
675
676  // Subtract this as if this portion does not exist.
677  free_space -= kMinFreeSpace;
678  return (free_space >= num_bytes);
679}
680
681bool FileCache::ImportOldDB(const base::FilePath& old_db_path) {
682  if (!base::PathExists(old_db_path))  // Old DB is not there, do nothing.
683    return false;
684
685  // Copy all entries stored in the old DB.
686  bool imported = false;
687  {
688    FileCacheMetadata old_data(blocking_task_runner_.get());
689    if (old_data.Initialize(old_db_path) ==
690        FileCacheMetadata::INITIALIZE_OPENED) {
691      scoped_ptr<FileCacheMetadata::Iterator> it = old_data.GetIterator();
692      for (; !it->IsAtEnd(); it->Advance()) {
693        FileCacheEntry entry;
694        if (storage_->GetCacheEntry(it->GetKey(), &entry))
695          continue;  // Do not overwrite.
696
697        storage_->PutCacheEntry(it->GetKey(), it->GetValue());
698      }
699      imported = true;
700    }
701  }
702
703  // Delete old DB.
704  base::DeleteFile(old_db_path, true /* recursive */ );
705  return imported;
706}
707
708void FileCache::RenameCacheFilesToNewFormat() {
709  // First, remove all files with multiple extensions just in case.
710  {
711    base::FileEnumerator enumerator(cache_file_directory_,
712                                    false,  // not recursive
713                                    base::FileEnumerator::FILES,
714                                    "*.*.*");
715    for (base::FilePath current = enumerator.Next(); !current.empty();
716         current = enumerator.Next())
717      base::DeleteFile(current, false /* recursive */);
718  }
719
720  // Rename files.
721  {
722    base::FileEnumerator enumerator(cache_file_directory_,
723                                    false,  // not recursive
724                                    base::FileEnumerator::FILES);
725    for (base::FilePath current = enumerator.Next(); !current.empty();
726         current = enumerator.Next())
727      base::Move(current, current.RemoveExtension());
728  }
729}
730
731}  // namespace internal
732}  // namespace drive
733