Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / chrome / browser / chromeos / drive / file_cache.cc
blob9ffd827bb56f8e0929ea9a377a9240cce7897879
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.
5 #include "chrome/browser/chromeos/drive/file_cache.h"
7 #include <vector>
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/callback_helpers.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/metrics/histogram.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/sys_info.h"
20 #include "chrome/browser/chromeos/drive/file_system_core_util.h"
21 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
22 #include "chromeos/chromeos_constants.h"
23 #include "components/drive/drive.pb.h"
24 #include "components/drive/drive_api_util.h"
25 #include "google_apis/drive/task_util.h"
26 #include "net/base/filename_util.h"
27 #include "net/base/mime_sniffer.h"
28 #include "net/base/mime_util.h"
29 #include "third_party/cros_system_api/constants/cryptohome.h"
31 namespace drive {
32 namespace internal {
33 namespace {
35 // Returns ID extracted from the path.
36 std::string GetIdFromPath(const base::FilePath& path) {
37 return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
40 } // namespace
42 FileCache::FileCache(ResourceMetadataStorage* storage,
43 const base::FilePath& cache_file_directory,
44 base::SequencedTaskRunner* blocking_task_runner,
45 FreeDiskSpaceGetterInterface* free_disk_space_getter)
46 : cache_file_directory_(cache_file_directory),
47 blocking_task_runner_(blocking_task_runner),
48 storage_(storage),
49 free_disk_space_getter_(free_disk_space_getter),
50 weak_ptr_factory_(this) {
51 DCHECK(blocking_task_runner_.get());
54 FileCache::~FileCache() {
55 // Must be on the sequenced worker pool, as |metadata_| must be deleted on
56 // the sequenced worker pool.
57 AssertOnSequencedWorkerPool();
60 base::FilePath FileCache::GetCacheFilePath(const std::string& id) const {
61 return cache_file_directory_.Append(
62 base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id)));
65 void FileCache::AssertOnSequencedWorkerPool() {
66 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
69 bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const {
70 return cache_file_directory_.IsParent(path);
73 bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
74 AssertOnSequencedWorkerPool();
76 // Do nothing and return if we have enough space.
77 if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
78 return true;
80 // Otherwise, try to free up the disk space.
81 DVLOG(1) << "Freeing up disk space for " << num_bytes;
83 // Remove all entries unless specially marked.
84 scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator();
85 for (; !it->IsAtEnd(); it->Advance()) {
86 if (it->GetValue().file_specific_info().has_cache_state() &&
87 !it->GetValue().file_specific_info().cache_state().is_pinned() &&
88 !it->GetValue().file_specific_info().cache_state().is_dirty() &&
89 !mounted_files_.count(it->GetID())) {
90 ResourceEntry entry(it->GetValue());
91 entry.mutable_file_specific_info()->clear_cache_state();
92 storage_->PutEntry(entry);
95 if (it->HasError())
96 return false;
98 // Remove all files which have no corresponding cache entries.
99 base::FileEnumerator enumerator(cache_file_directory_,
100 false, // not recursive
101 base::FileEnumerator::FILES);
102 ResourceEntry entry;
103 for (base::FilePath current = enumerator.Next(); !current.empty();
104 current = enumerator.Next()) {
105 std::string id = GetIdFromPath(current);
106 FileError error = storage_->GetEntry(id, &entry);
107 if (error == FILE_ERROR_NOT_FOUND ||
108 (error == FILE_ERROR_OK &&
109 !entry.file_specific_info().cache_state().is_present()))
110 base::DeleteFile(current, false /* recursive */);
111 else if (error != FILE_ERROR_OK)
112 return false;
115 // Check the disk space again.
116 return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
119 FileError FileCache::GetFile(const std::string& id,
120 base::FilePath* cache_file_path) {
121 AssertOnSequencedWorkerPool();
122 DCHECK(cache_file_path);
124 ResourceEntry entry;
125 FileError error = storage_->GetEntry(id, &entry);
126 if (error != FILE_ERROR_OK)
127 return error;
128 if (!entry.file_specific_info().cache_state().is_present())
129 return FILE_ERROR_NOT_FOUND;
131 *cache_file_path = GetCacheFilePath(id);
132 return FILE_ERROR_OK;
135 FileError FileCache::Store(const std::string& id,
136 const std::string& md5,
137 const base::FilePath& source_path,
138 FileOperationType file_operation_type) {
139 AssertOnSequencedWorkerPool();
141 ResourceEntry entry;
142 FileError error = storage_->GetEntry(id, &entry);
143 if (error != FILE_ERROR_OK)
144 return error;
146 int64 file_size = 0;
147 if (file_operation_type == FILE_OPERATION_COPY) {
148 if (!base::GetFileSize(source_path, &file_size)) {
149 LOG(WARNING) << "Couldn't get file size for: " << source_path.value();
150 return FILE_ERROR_FAILED;
153 if (!FreeDiskSpaceIfNeededFor(file_size))
154 return FILE_ERROR_NO_LOCAL_SPACE;
156 // If file is mounted, return error.
157 if (mounted_files_.count(id))
158 return FILE_ERROR_IN_USE;
160 base::FilePath dest_path = GetCacheFilePath(id);
161 bool success = false;
162 switch (file_operation_type) {
163 case FILE_OPERATION_MOVE:
164 success = base::Move(source_path, dest_path);
165 break;
166 case FILE_OPERATION_COPY:
167 success = base::CopyFile(source_path, dest_path);
168 break;
169 default:
170 NOTREACHED();
173 if (!success) {
174 LOG(ERROR) << "Failed to store: "
175 << "source_path = " << source_path.value() << ", "
176 << "dest_path = " << dest_path.value() << ", "
177 << "file_operation_type = " << file_operation_type;
178 return FILE_ERROR_FAILED;
181 // Now that file operations have completed, update metadata.
182 FileCacheEntry* cache_state =
183 entry.mutable_file_specific_info()->mutable_cache_state();
184 cache_state->set_md5(md5);
185 cache_state->set_is_present(true);
186 if (md5.empty())
187 cache_state->set_is_dirty(true);
188 return storage_->PutEntry(entry);
191 FileError FileCache::Pin(const std::string& id) {
192 AssertOnSequencedWorkerPool();
194 ResourceEntry entry;
195 FileError error = storage_->GetEntry(id, &entry);
196 if (error != FILE_ERROR_OK)
197 return error;
198 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned(
199 true);
200 return storage_->PutEntry(entry);
203 FileError FileCache::Unpin(const std::string& id) {
204 AssertOnSequencedWorkerPool();
206 // Unpinning a file means its entry must exist in cache.
207 ResourceEntry entry;
208 FileError error = storage_->GetEntry(id, &entry);
209 if (error != FILE_ERROR_OK)
210 return error;
212 // Now that file operations have completed, update metadata.
213 if (entry.file_specific_info().cache_state().is_present()) {
214 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned(
215 false);
216 } else {
217 // Remove the existing entry if we are unpinning a non-present file.
218 entry.mutable_file_specific_info()->clear_cache_state();
220 error = storage_->PutEntry(entry);
221 if (error != FILE_ERROR_OK)
222 return error;
224 // Now it's a chance to free up space if needed.
225 FreeDiskSpaceIfNeededFor(0);
227 return FILE_ERROR_OK;
230 FileError FileCache::MarkAsMounted(const std::string& id,
231 base::FilePath* cache_file_path) {
232 AssertOnSequencedWorkerPool();
233 DCHECK(cache_file_path);
235 // Get cache entry associated with the id and md5
236 ResourceEntry entry;
237 FileError error = storage_->GetEntry(id, &entry);
238 if (error != FILE_ERROR_OK)
239 return error;
240 if (!entry.file_specific_info().cache_state().is_present())
241 return FILE_ERROR_NOT_FOUND;
243 if (mounted_files_.count(id))
244 return FILE_ERROR_INVALID_OPERATION;
246 // Ensure the file is readable to cros_disks. See crbug.com/236994.
247 base::FilePath path = GetCacheFilePath(id);
248 if (!base::SetPosixFilePermissions(
249 path,
250 base::FILE_PERMISSION_READ_BY_USER |
251 base::FILE_PERMISSION_WRITE_BY_USER |
252 base::FILE_PERMISSION_READ_BY_GROUP |
253 base::FILE_PERMISSION_READ_BY_OTHERS))
254 return FILE_ERROR_FAILED;
256 mounted_files_.insert(id);
258 *cache_file_path = path;
259 return FILE_ERROR_OK;
262 FileError FileCache::OpenForWrite(
263 const std::string& id,
264 scoped_ptr<base::ScopedClosureRunner>* file_closer) {
265 AssertOnSequencedWorkerPool();
267 // Marking a file dirty means its entry and actual file blob must exist in
268 // cache.
269 ResourceEntry entry;
270 FileError error = storage_->GetEntry(id, &entry);
271 if (error != FILE_ERROR_OK)
272 return error;
273 if (!entry.file_specific_info().cache_state().is_present()) {
274 LOG(WARNING) << "Can't mark dirty a file that wasn't cached: " << id;
275 return FILE_ERROR_NOT_FOUND;
278 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(true);
279 entry.mutable_file_specific_info()->mutable_cache_state()->clear_md5();
280 error = storage_->PutEntry(entry);
281 if (error != FILE_ERROR_OK)
282 return error;
284 write_opened_files_[id]++;
285 file_closer->reset(new base::ScopedClosureRunner(
286 base::Bind(&google_apis::RunTaskWithTaskRunner,
287 blocking_task_runner_,
288 base::Bind(&FileCache::CloseForWrite,
289 weak_ptr_factory_.GetWeakPtr(),
290 id))));
291 return FILE_ERROR_OK;
294 bool FileCache::IsOpenedForWrite(const std::string& id) {
295 AssertOnSequencedWorkerPool();
296 return write_opened_files_.count(id) != 0;
299 FileError FileCache::UpdateMd5(const std::string& id) {
300 AssertOnSequencedWorkerPool();
302 if (IsOpenedForWrite(id))
303 return FILE_ERROR_IN_USE;
305 ResourceEntry entry;
306 FileError error = storage_->GetEntry(id, &entry);
307 if (error != FILE_ERROR_OK)
308 return error;
309 if (!entry.file_specific_info().cache_state().is_present())
310 return FILE_ERROR_NOT_FOUND;
312 const std::string& md5 =
313 util::GetMd5Digest(GetCacheFilePath(id), &in_shutdown_);
314 if (in_shutdown_.IsSet())
315 return FILE_ERROR_ABORT;
316 if (md5.empty())
317 return FILE_ERROR_NOT_FOUND;
319 entry.mutable_file_specific_info()->mutable_cache_state()->set_md5(md5);
320 return storage_->PutEntry(entry);
323 FileError FileCache::ClearDirty(const std::string& id) {
324 AssertOnSequencedWorkerPool();
326 if (IsOpenedForWrite(id))
327 return FILE_ERROR_IN_USE;
329 // Clearing a dirty file means its entry and actual file blob must exist in
330 // cache.
331 ResourceEntry entry;
332 FileError error = storage_->GetEntry(id, &entry);
333 if (error != FILE_ERROR_OK)
334 return error;
335 if (!entry.file_specific_info().cache_state().is_present()) {
336 LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
337 << id;
338 return FILE_ERROR_NOT_FOUND;
341 // If a file is not dirty (it should have been marked dirty via OpenForWrite),
342 // clearing its dirty state is an invalid operation.
343 if (!entry.file_specific_info().cache_state().is_dirty()) {
344 LOG(WARNING) << "Can't clear dirty state of a non-dirty file: " << id;
345 return FILE_ERROR_INVALID_OPERATION;
348 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(
349 false);
350 return storage_->PutEntry(entry);
353 FileError FileCache::Remove(const std::string& id) {
354 AssertOnSequencedWorkerPool();
356 ResourceEntry entry;
358 // If entry doesn't exist, nothing to do.
359 FileError error = storage_->GetEntry(id, &entry);
360 if (error == FILE_ERROR_NOT_FOUND)
361 return FILE_ERROR_OK;
362 if (error != FILE_ERROR_OK)
363 return error;
364 if (!entry.file_specific_info().has_cache_state())
365 return FILE_ERROR_OK;
367 // Cannot delete a mounted file.
368 if (mounted_files_.count(id))
369 return FILE_ERROR_IN_USE;
371 // Delete the file.
372 base::FilePath path = GetCacheFilePath(id);
373 if (!base::DeleteFile(path, false /* recursive */))
374 return FILE_ERROR_FAILED;
376 // Now that all file operations have completed, remove from metadata.
377 entry.mutable_file_specific_info()->clear_cache_state();
378 return storage_->PutEntry(entry);
381 bool FileCache::ClearAll() {
382 AssertOnSequencedWorkerPool();
384 // Remove files.
385 base::FileEnumerator enumerator(cache_file_directory_,
386 false, // not recursive
387 base::FileEnumerator::FILES);
388 for (base::FilePath file = enumerator.Next(); !file.empty();
389 file = enumerator.Next())
390 base::DeleteFile(file, false /* recursive */);
392 return true;
395 bool FileCache::Initialize() {
396 AssertOnSequencedWorkerPool();
398 // Older versions do not clear MD5 when marking entries dirty.
399 // Clear MD5 of all dirty entries to deal with old data.
400 scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator();
401 for (; !it->IsAtEnd(); it->Advance()) {
402 if (it->GetValue().file_specific_info().cache_state().is_dirty()) {
403 ResourceEntry new_entry(it->GetValue());
404 new_entry.mutable_file_specific_info()->mutable_cache_state()->
405 clear_md5();
406 if (storage_->PutEntry(new_entry) != FILE_ERROR_OK)
407 return false;
410 if (it->HasError())
411 return false;
413 if (!RenameCacheFilesToNewFormat())
414 return false;
415 return true;
418 void FileCache::Destroy() {
419 DCHECK(thread_checker_.CalledOnValidThread());
421 in_shutdown_.Set();
423 // Destroy myself on the blocking pool.
424 // Note that base::DeletePointer<> cannot be used as the destructor of this
425 // class is private.
426 blocking_task_runner_->PostTask(
427 FROM_HERE,
428 base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this)));
431 void FileCache::DestroyOnBlockingPool() {
432 AssertOnSequencedWorkerPool();
433 delete this;
436 bool FileCache::RecoverFilesFromCacheDirectory(
437 const base::FilePath& dest_directory,
438 const ResourceMetadataStorage::RecoveredCacheInfoMap&
439 recovered_cache_info) {
440 int file_number = 1;
442 base::FileEnumerator enumerator(cache_file_directory_,
443 false, // not recursive
444 base::FileEnumerator::FILES);
445 for (base::FilePath current = enumerator.Next(); !current.empty();
446 current = enumerator.Next()) {
447 const std::string& id = GetIdFromPath(current);
448 ResourceEntry entry;
449 FileError error = storage_->GetEntry(id, &entry);
450 if (error != FILE_ERROR_OK && error != FILE_ERROR_NOT_FOUND)
451 return false;
452 if (error == FILE_ERROR_OK &&
453 entry.file_specific_info().cache_state().is_present()) {
454 // This file is managed by FileCache, no need to recover it.
455 continue;
458 // If a cache entry which is non-dirty and has matching MD5 is found in
459 // |recovered_cache_entries|, it means the current file is already uploaded
460 // to the server. Just delete it instead of recovering it.
461 ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it =
462 recovered_cache_info.find(id);
463 if (it != recovered_cache_info.end()) {
464 // Due to the DB corruption, cache info might be recovered from old
465 // revision. Perform MD5 check even when is_dirty is false just in case.
466 if (!it->second.is_dirty &&
467 it->second.md5 == util::GetMd5Digest(current, &in_shutdown_)) {
468 base::DeleteFile(current, false /* recursive */);
469 continue;
473 // Read file contents to sniff mime type.
474 std::vector<char> content(net::kMaxBytesToSniff);
475 const int read_result =
476 base::ReadFile(current, &content[0], content.size());
477 if (read_result < 0) {
478 LOG(WARNING) << "Cannot read: " << current.value();
479 return false;
481 if (read_result == 0) // Skip empty files.
482 continue;
484 // Use recovered file name if available, otherwise decide file name with
485 // sniffed mime type.
486 base::FilePath dest_base_name(FILE_PATH_LITERAL("file"));
487 std::string mime_type;
488 if (it != recovered_cache_info.end() && !it->second.title.empty()) {
489 // We can use a file name recovered from the trashed DB.
490 dest_base_name = base::FilePath::FromUTF8Unsafe(it->second.title);
491 } else if (net::SniffMimeType(&content[0], read_result,
492 net::FilePathToFileURL(current),
493 std::string(), &mime_type) ||
494 net::SniffMimeTypeFromLocalData(&content[0], read_result,
495 &mime_type)) {
496 // Change base name for common mime types.
497 if (net::MatchesMimeType("image/*", mime_type)) {
498 dest_base_name = base::FilePath(FILE_PATH_LITERAL("image"));
499 } else if (net::MatchesMimeType("video/*", mime_type)) {
500 dest_base_name = base::FilePath(FILE_PATH_LITERAL("video"));
501 } else if (net::MatchesMimeType("audio/*", mime_type)) {
502 dest_base_name = base::FilePath(FILE_PATH_LITERAL("audio"));
505 // Estimate extension from mime type.
506 std::vector<base::FilePath::StringType> extensions;
507 base::FilePath::StringType extension;
508 if (net::GetPreferredExtensionForMimeType(mime_type, &extension))
509 extensions.push_back(extension);
510 else
511 net::GetExtensionsForMimeType(mime_type, &extensions);
513 // Add extension if possible.
514 if (!extensions.empty())
515 dest_base_name = dest_base_name.AddExtension(extensions[0]);
518 // Add file number to the file name and move.
519 const base::FilePath& dest_path = dest_directory.Append(dest_base_name)
520 .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number++));
521 if (!base::CreateDirectory(dest_directory) ||
522 !base::Move(current, dest_path)) {
523 LOG(WARNING) << "Failed to move: " << current.value()
524 << " to " << dest_path.value();
525 return false;
528 UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption",
529 file_number - 1);
530 return true;
533 FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) {
534 AssertOnSequencedWorkerPool();
535 DCHECK(IsUnderFileCacheDirectory(file_path));
537 std::string id = GetIdFromPath(file_path);
539 // Get the entry associated with the id.
540 ResourceEntry entry;
541 FileError error = storage_->GetEntry(id, &entry);
542 if (error != FILE_ERROR_OK)
543 return error;
545 std::set<std::string>::iterator it = mounted_files_.find(id);
546 if (it == mounted_files_.end())
547 return FILE_ERROR_INVALID_OPERATION;
549 mounted_files_.erase(it);
550 return FILE_ERROR_OK;
553 bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
554 const base::FilePath& path) {
555 int64 free_space = 0;
556 if (free_disk_space_getter_)
557 free_space = free_disk_space_getter_->AmountOfFreeDiskSpace();
558 else
559 free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
561 // Subtract this as if this portion does not exist.
562 free_space -= cryptohome::kMinFreeSpaceInBytes;
563 return (free_space >= num_bytes);
566 bool FileCache::RenameCacheFilesToNewFormat() {
567 base::FileEnumerator enumerator(cache_file_directory_,
568 false, // not recursive
569 base::FileEnumerator::FILES);
570 for (base::FilePath current = enumerator.Next(); !current.empty();
571 current = enumerator.Next()) {
572 base::FilePath new_path = current.RemoveExtension();
573 if (!new_path.Extension().empty()) {
574 // Delete files with multiple extensions.
575 if (!base::DeleteFile(current, false /* recursive */))
576 return false;
577 continue;
579 const std::string& id = GetIdFromPath(new_path);
580 new_path = GetCacheFilePath(util::CanonicalizeResourceId(id));
581 if (new_path != current && !base::Move(current, new_path))
582 return false;
584 return true;
587 void FileCache::CloseForWrite(const std::string& id) {
588 AssertOnSequencedWorkerPool();
590 std::map<std::string, int>::iterator it = write_opened_files_.find(id);
591 if (it == write_opened_files_.end())
592 return;
594 DCHECK_LT(0, it->second);
595 --it->second;
596 if (it->second == 0)
597 write_opened_files_.erase(it);
599 // Update last modified date.
600 ResourceEntry entry;
601 FileError error = storage_->GetEntry(id, &entry);
602 if (error != FILE_ERROR_OK) {
603 LOG(ERROR) << "Failed to get entry: " << id << ", "
604 << FileErrorToString(error);
605 return;
607 entry.mutable_file_info()->set_last_modified(
608 base::Time::Now().ToInternalValue());
609 error = storage_->PutEntry(entry);
610 if (error != FILE_ERROR_OK) {
611 LOG(ERROR) << "Failed to put entry: " << id << ", "
612 << FileErrorToString(error);
616 } // namespace internal
617 } // namespace drive