OLD | NEW |
| (Empty) |
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/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" | |
30 | |
31 namespace drive { | |
32 namespace internal { | |
33 namespace { | |
34 | |
35 // Returns ID extracted from the path. | |
36 std::string GetIdFromPath(const base::FilePath& path) { | |
37 return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe()); | |
38 } | |
39 | |
40 } // namespace | |
41 | |
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()); | |
52 } | |
53 | |
54 FileCache::~FileCache() { | |
55 // Must be on the sequenced worker pool, as |metadata_| must be deleted on | |
56 // the sequenced worker pool. | |
57 AssertOnSequencedWorkerPool(); | |
58 } | |
59 | |
60 base::FilePath FileCache::GetCacheFilePath(const std::string& id) const { | |
61 return cache_file_directory_.Append( | |
62 base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id))); | |
63 } | |
64 | |
65 void FileCache::AssertOnSequencedWorkerPool() { | |
66 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); | |
67 } | |
68 | |
69 bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const { | |
70 return cache_file_directory_.IsParent(path); | |
71 } | |
72 | |
73 bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) { | |
74 AssertOnSequencedWorkerPool(); | |
75 | |
76 // Do nothing and return if we have enough space. | |
77 if (HasEnoughSpaceFor(num_bytes, cache_file_directory_)) | |
78 return true; | |
79 | |
80 // Otherwise, try to free up the disk space. | |
81 DVLOG(1) << "Freeing up disk space for " << num_bytes; | |
82 | |
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); | |
93 } | |
94 } | |
95 if (it->HasError()) | |
96 return false; | |
97 | |
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; | |
113 } | |
114 | |
115 // Check the disk space again. | |
116 return HasEnoughSpaceFor(num_bytes, cache_file_directory_); | |
117 } | |
118 | |
119 FileError FileCache::GetFile(const std::string& id, | |
120 base::FilePath* cache_file_path) { | |
121 AssertOnSequencedWorkerPool(); | |
122 DCHECK(cache_file_path); | |
123 | |
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; | |
130 | |
131 *cache_file_path = GetCacheFilePath(id); | |
132 return FILE_ERROR_OK; | |
133 } | |
134 | |
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(); | |
140 | |
141 ResourceEntry entry; | |
142 FileError error = storage_->GetEntry(id, &entry); | |
143 if (error != FILE_ERROR_OK) | |
144 return error; | |
145 | |
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; | |
151 } | |
152 } | |
153 if (!FreeDiskSpaceIfNeededFor(file_size)) | |
154 return FILE_ERROR_NO_LOCAL_SPACE; | |
155 | |
156 // If file is mounted, return error. | |
157 if (mounted_files_.count(id)) | |
158 return FILE_ERROR_IN_USE; | |
159 | |
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(); | |
171 } | |
172 | |
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; | |
179 } | |
180 | |
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); | |
189 } | |
190 | |
191 FileError FileCache::Pin(const std::string& id) { | |
192 AssertOnSequencedWorkerPool(); | |
193 | |
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); | |
201 } | |
202 | |
203 FileError FileCache::Unpin(const std::string& id) { | |
204 AssertOnSequencedWorkerPool(); | |
205 | |
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; | |
211 | |
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(); | |
219 } | |
220 error = storage_->PutEntry(entry); | |
221 if (error != FILE_ERROR_OK) | |
222 return error; | |
223 | |
224 // Now it's a chance to free up space if needed. | |
225 FreeDiskSpaceIfNeededFor(0); | |
226 | |
227 return FILE_ERROR_OK; | |
228 } | |
229 | |
230 FileError FileCache::MarkAsMounted(const std::string& id, | |
231 base::FilePath* cache_file_path) { | |
232 AssertOnSequencedWorkerPool(); | |
233 DCHECK(cache_file_path); | |
234 | |
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; | |
242 | |
243 if (mounted_files_.count(id)) | |
244 return FILE_ERROR_INVALID_OPERATION; | |
245 | |
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; | |
255 | |
256 mounted_files_.insert(id); | |
257 | |
258 *cache_file_path = path; | |
259 return FILE_ERROR_OK; | |
260 } | |
261 | |
262 FileError FileCache::OpenForWrite( | |
263 const std::string& id, | |
264 scoped_ptr<base::ScopedClosureRunner>* file_closer) { | |
265 AssertOnSequencedWorkerPool(); | |
266 | |
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; | |
276 } | |
277 | |
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; | |
283 | |
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; | |
292 } | |
293 | |
294 bool FileCache::IsOpenedForWrite(const std::string& id) { | |
295 AssertOnSequencedWorkerPool(); | |
296 return write_opened_files_.count(id) != 0; | |
297 } | |
298 | |
299 FileError FileCache::UpdateMd5(const std::string& id) { | |
300 AssertOnSequencedWorkerPool(); | |
301 | |
302 if (IsOpenedForWrite(id)) | |
303 return FILE_ERROR_IN_USE; | |
304 | |
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; | |
311 | |
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; | |
318 | |
319 entry.mutable_file_specific_info()->mutable_cache_state()->set_md5(md5); | |
320 return storage_->PutEntry(entry); | |
321 } | |
322 | |
323 FileError FileCache::ClearDirty(const std::string& id) { | |
324 AssertOnSequencedWorkerPool(); | |
325 | |
326 if (IsOpenedForWrite(id)) | |
327 return FILE_ERROR_IN_USE; | |
328 | |
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; | |
339 } | |
340 | |
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; | |
346 } | |
347 | |
348 entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty( | |
349 false); | |
350 return storage_->PutEntry(entry); | |
351 } | |
352 | |
353 FileError FileCache::Remove(const std::string& id) { | |
354 AssertOnSequencedWorkerPool(); | |
355 | |
356 ResourceEntry entry; | |
357 | |
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; | |
366 | |
367 // Cannot delete a mounted file. | |
368 if (mounted_files_.count(id)) | |
369 return FILE_ERROR_IN_USE; | |
370 | |
371 // Delete the file. | |
372 base::FilePath path = GetCacheFilePath(id); | |
373 if (!base::DeleteFile(path, false /* recursive */)) | |
374 return FILE_ERROR_FAILED; | |
375 | |
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); | |
379 } | |
380 | |
381 bool FileCache::ClearAll() { | |
382 AssertOnSequencedWorkerPool(); | |
383 | |
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 */); | |
391 | |
392 return true; | |
393 } | |
394 | |
395 bool FileCache::Initialize() { | |
396 AssertOnSequencedWorkerPool(); | |
397 | |
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; | |
408 } | |
409 } | |
410 if (it->HasError()) | |
411 return false; | |
412 | |
413 if (!RenameCacheFilesToNewFormat()) | |
414 return false; | |
415 return true; | |
416 } | |
417 | |
418 void FileCache::Destroy() { | |
419 DCHECK(thread_checker_.CalledOnValidThread()); | |
420 | |
421 in_shutdown_.Set(); | |
422 | |
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))); | |
429 } | |
430 | |
431 void FileCache::DestroyOnBlockingPool() { | |
432 AssertOnSequencedWorkerPool(); | |
433 delete this; | |
434 } | |
435 | |
436 bool FileCache::RecoverFilesFromCacheDirectory( | |
437 const base::FilePath& dest_directory, | |
438 const ResourceMetadataStorage::RecoveredCacheInfoMap& | |
439 recovered_cache_info) { | |
440 int file_number = 1; | |
441 | |
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; | |
456 } | |
457 | |
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; | |
470 } | |
471 } | |
472 | |
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; | |
480 } | |
481 if (read_result == 0) // Skip empty files. | |
482 continue; | |
483 | |
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")); | |
503 } | |
504 | |
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); | |
512 | |
513 // Add extension if possible. | |
514 if (!extensions.empty()) | |
515 dest_base_name = dest_base_name.AddExtension(extensions[0]); | |
516 } | |
517 | |
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; | |
526 } | |
527 } | |
528 UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption", | |
529 file_number - 1); | |
530 return true; | |
531 } | |
532 | |
533 FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) { | |
534 AssertOnSequencedWorkerPool(); | |
535 DCHECK(IsUnderFileCacheDirectory(file_path)); | |
536 | |
537 std::string id = GetIdFromPath(file_path); | |
538 | |
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; | |
544 | |
545 std::set<std::string>::iterator it = mounted_files_.find(id); | |
546 if (it == mounted_files_.end()) | |
547 return FILE_ERROR_INVALID_OPERATION; | |
548 | |
549 mounted_files_.erase(it); | |
550 return FILE_ERROR_OK; | |
551 } | |
552 | |
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); | |
560 | |
561 // Subtract this as if this portion does not exist. | |
562 free_space -= cryptohome::kMinFreeSpaceInBytes; | |
563 return (free_space >= num_bytes); | |
564 } | |
565 | |
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; | |
578 } | |
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; | |
583 } | |
584 return true; | |
585 } | |
586 | |
587 void FileCache::CloseForWrite(const std::string& id) { | |
588 AssertOnSequencedWorkerPool(); | |
589 | |
590 std::map<std::string, int>::iterator it = write_opened_files_.find(id); | |
591 if (it == write_opened_files_.end()) | |
592 return; | |
593 | |
594 DCHECK_LT(0, it->second); | |
595 --it->second; | |
596 if (it->second == 0) | |
597 write_opened_files_.erase(it); | |
598 | |
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; | |
606 } | |
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); | |
613 } | |
614 } | |
615 | |
616 } // namespace internal | |
617 } // namespace drive | |
OLD | NEW |