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