blob: 9c073250c23410da69093eec34a0d95ad9996865 [file] [log] [blame]
[email protected]3653146a2012-05-29 13:41:471// 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/gdata/gdata_cache.h"
6
7#include <vector>
8
[email protected]a321b9632012-06-14 03:29:179#include "base/chromeos/chromeos_version.h"
10#include "base/file_util.h"
[email protected]3653146a2012-05-29 13:41:4711#include "base/logging.h"
12#include "base/stringprintf.h"
13#include "base/string_util.h"
[email protected]a321b9632012-06-14 03:29:1714#include "base/sys_info.h"
[email protected]ca5f6da2012-06-18 12:54:5915#include "chrome/browser/chromeos/gdata/gdata_cache_metadata.h"
[email protected]32a7fc852012-06-08 17:25:5016#include "chrome/browser/chromeos/gdata/gdata_util.h"
[email protected]01ba15f72012-06-09 00:41:0517#include "chrome/browser/profiles/profile.h"
18#include "chrome/common/chrome_constants.h"
19#include "chrome/common/chrome_paths_internal.h"
[email protected]7986b8d2012-06-14 15:05:1420#include "content/public/browser/browser_thread.h"
21
22using content::BrowserThread;
[email protected]3653146a2012-05-29 13:41:4723
24namespace gdata {
25namespace {
26
[email protected]a321b9632012-06-14 03:29:1727const FilePath::CharType kSymLinkToDevNull[] = FILE_PATH_LITERAL("/dev/null");
28
[email protected]32a7fc852012-06-08 17:25:5029const char kLocallyModifiedFileExtension[] = "local";
30
[email protected]01ba15f72012-06-09 00:41:0531const FilePath::CharType kGDataCacheVersionDir[] = FILE_PATH_LITERAL("v1");
[email protected]32a7fc852012-06-08 17:25:5032const FilePath::CharType kGDataCacheMetaDir[] = FILE_PATH_LITERAL("meta");
33const FilePath::CharType kGDataCachePinnedDir[] = FILE_PATH_LITERAL("pinned");
34const FilePath::CharType kGDataCacheOutgoingDir[] =
35 FILE_PATH_LITERAL("outgoing");
36const FilePath::CharType kGDataCachePersistentDir[] =
37 FILE_PATH_LITERAL("persistent");
38const FilePath::CharType kGDataCacheTmpDir[] = FILE_PATH_LITERAL("tmp");
39const FilePath::CharType kGDataCacheTmpDownloadsDir[] =
40 FILE_PATH_LITERAL("tmp/downloads");
41const FilePath::CharType kGDataCacheTmpDocumentsDir[] =
42 FILE_PATH_LITERAL("tmp/documents");
43
[email protected]3653146a2012-05-29 13:41:4744std::string CacheSubDirectoryTypeToString(
45 GDataCache::CacheSubDirectoryType subdir) {
46 switch (subdir) {
47 case GDataCache::CACHE_TYPE_META:
48 return "meta";
49 case GDataCache::CACHE_TYPE_PINNED:
50 return "pinned";
51 case GDataCache::CACHE_TYPE_OUTGOING:
52 return "outgoing";
53 case GDataCache::CACHE_TYPE_PERSISTENT:
54 return "persistent";
55 case GDataCache::CACHE_TYPE_TMP:
56 return "tmp";
57 case GDataCache::CACHE_TYPE_TMP_DOWNLOADS:
58 return "tmp_downloads";
59 case GDataCache::CACHE_TYPE_TMP_DOCUMENTS:
60 return "tmp_documents";
61 case GDataCache::NUM_CACHE_TYPES:
62 NOTREACHED();
63 }
64 NOTREACHED();
65 return "unknown subdir";
66}
67
[email protected]6b70c7b2012-06-14 03:10:4368// Returns file paths for all the cache sub directories under
69// |cache_root_path|.
70std::vector<FilePath> GetCachePaths(const FilePath& cache_root_path) {
71 std::vector<FilePath> cache_paths;
72 // The order should match GDataCache::CacheSubDirectoryType enum.
73 cache_paths.push_back(cache_root_path.Append(kGDataCacheMetaDir));
74 cache_paths.push_back(cache_root_path.Append(kGDataCachePinnedDir));
75 cache_paths.push_back(cache_root_path.Append(kGDataCacheOutgoingDir));
76 cache_paths.push_back(cache_root_path.Append(kGDataCachePersistentDir));
77 cache_paths.push_back(cache_root_path.Append(kGDataCacheTmpDir));
78 cache_paths.push_back(cache_root_path.Append(kGDataCacheTmpDownloadsDir));
79 cache_paths.push_back(cache_root_path.Append(kGDataCacheTmpDocumentsDir));
80 return cache_paths;
81}
82
[email protected]a321b9632012-06-14 03:29:1783// Returns the home directory path, or an empty string if the home directory
84// is not found.
85// Copied from webkit/chromeos/cros_mount_point_provider.h.
86// TODO(satorux): Share the code.
87std::string GetHomeDirectory() {
88 if (base::chromeos::IsRunningOnChromeOS())
89 return "/home/chronos/user";
[email protected]3653146a2012-05-29 13:41:4790
[email protected]a321b9632012-06-14 03:29:1791 const char* home = getenv("HOME");
92 if (home)
93 return home;
94 return "";
95}
96
97// Used to tweak GetAmountOfFreeDiskSpace() behavior for testing.
98FreeDiskSpaceGetterInterface* global_free_disk_getter_for_testing = NULL;
99
100// Gets the amount of free disk space. Use
101// |global_free_disk_getter_for_testing| if set.
102int64 GetAmountOfFreeDiskSpace() {
103 if (global_free_disk_getter_for_testing)
104 return global_free_disk_getter_for_testing->AmountOfFreeDiskSpace();
105
106 return base::SysInfo::AmountOfFreeDiskSpace(
107 FilePath::FromUTF8Unsafe(GetHomeDirectory()));
108}
109
110// Returns true if we have sufficient space to store the given number of
111// bytes, while keeping kMinFreeSpace bytes on the disk.
112bool HasEnoughSpaceFor(int64 num_bytes) {
113 int64 free_space = GetAmountOfFreeDiskSpace();
114 // Substract this as if this portion does not exist.
115 free_space -= kMinFreeSpace;
116 return (free_space >= num_bytes);
117}
118
119// Remove all files under the given directory, non-recursively.
120// Do not remove recursively as we don't want to touch <gache>/tmp/downloads,
121// which is used for user initiated downloads like "Save As"
122void RemoveAllFiles(const FilePath& directory) {
123 using file_util::FileEnumerator;
124
125 FileEnumerator enumerator(directory, false /* recursive */,
126 FileEnumerator::FILES);
127 for (FilePath file_path = enumerator.Next(); !file_path.empty();
128 file_path = enumerator.Next()) {
129 DVLOG(1) << "Removing " << file_path.value();
130 if (!file_util::Delete(file_path, false /* recursive */))
131 LOG(WARNING) << "Failed to delete " << file_path.value();
132 }
133}
134
135// Converts system error to file platform error code.
136// This is copied and modified from base/platform_file_posix.cc.
137// TODO(satorux): Remove this copy-pasted function. crbug.com/132656
138base::PlatformFileError SystemToPlatformError(int error) {
139 switch (error) {
140 case 0:
141 return base::PLATFORM_FILE_OK;
142 case EACCES:
143 case EISDIR:
144 case EROFS:
145 case EPERM:
146 return base::PLATFORM_FILE_ERROR_ACCESS_DENIED;
147 case ETXTBSY:
148 return base::PLATFORM_FILE_ERROR_IN_USE;
149 case EEXIST:
150 return base::PLATFORM_FILE_ERROR_EXISTS;
151 case ENOENT:
152 return base::PLATFORM_FILE_ERROR_NOT_FOUND;
153 case EMFILE:
154 return base::PLATFORM_FILE_ERROR_TOO_MANY_OPENED;
155 case ENOMEM:
156 return base::PLATFORM_FILE_ERROR_NO_MEMORY;
157 case ENOSPC:
158 return base::PLATFORM_FILE_ERROR_NO_SPACE;
159 case ENOTDIR:
160 return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
161 case EINTR:
162 return base::PLATFORM_FILE_ERROR_ABORT;
163 default:
164 return base::PLATFORM_FILE_ERROR_FAILED;
165 }
166}
167
[email protected]a321b9632012-06-14 03:29:17168// Modifies cache state of file on blocking pool, which involves:
169// - moving or copying file (per |file_operation_type|) from |source_path| to
170// |dest_path| if they're different
171// - deleting symlink if |symlink_path| is not empty
172// - creating symlink if |symlink_path| is not empty and |create_symlink| is
173// true.
174base::PlatformFileError ModifyCacheState(
175 const FilePath& source_path,
176 const FilePath& dest_path,
177 GDataCache::FileOperationType file_operation_type,
178 const FilePath& symlink_path,
179 bool create_symlink) {
180 // Move or copy |source_path| to |dest_path| if they are different.
181 if (source_path != dest_path) {
182 bool success = false;
183 if (file_operation_type == GDataCache::FILE_OPERATION_MOVE)
184 success = file_util::Move(source_path, dest_path);
185 else if (file_operation_type == GDataCache::FILE_OPERATION_COPY)
186 success = file_util::CopyFile(source_path, dest_path);
187 if (!success) {
188 base::PlatformFileError error = SystemToPlatformError(errno);
189 PLOG(ERROR) << "Error "
190 << (file_operation_type == GDataCache::FILE_OPERATION_MOVE ?
191 "moving " : "copying ")
192 << source_path.value()
193 << " to " << dest_path.value();
194 return error;
195 } else {
196 DVLOG(1) << (file_operation_type == GDataCache::FILE_OPERATION_MOVE ?
197 "Moved " : "Copied ")
198 << source_path.value()
199 << " to " << dest_path.value();
200 }
201 } else {
202 DVLOG(1) << "No need to move file: source = destination";
203 }
204
205 if (symlink_path.empty())
206 return base::PLATFORM_FILE_OK;
207
208 // Remove symlink regardless of |create_symlink| because creating a link will
209 // not overwrite an existing one.
210
211 // Cannot use file_util::Delete which uses stat64 to check if path exists
212 // before deleting it. If path is a symlink, stat64 dereferences it to the
213 // target file, so it's in essence checking if the target file exists.
214 // Here in this function, if |symlink_path| references |source_path| and
215 // |source_path| has just been moved to |dest_path| (e.g. during unpinning),
216 // symlink will dereference to a non-existent file. This results in stat64
217 // failing and file_util::Delete bailing out without deleting the symlink.
218 // We clearly want the symlink deleted even if it dereferences to nothing.
219 // Unfortunately, deleting the symlink before moving the files won't work for
220 // the case where move operation fails, but the symlink has already been
221 // deleted, which shouldn't happen. An example scenario is where an existing
222 // file is stored to cache and pinned for a specific resource id and md5, then
223 // a non-existent file is stored to cache for the same resource id and md5.
224 // The 2nd store-to-cache operation fails when moving files, but the symlink
225 // created by previous pin operation has already been deleted.
226 // We definitely want to keep the pinned state of the symlink if subsequent
227 // operations fail.
228 // This problem is filed at http://crbug.com/119430.
229
230 // We try to save one file operation by not checking if link exists before
231 // deleting it, so unlink may return error if link doesn't exist, but it
232 // doesn't really matter to us.
233 bool deleted = HANDLE_EINTR(unlink(symlink_path.value().c_str())) == 0;
234 if (deleted) {
235 DVLOG(1) << "Deleted symlink " << symlink_path.value();
236 } else {
237 // Since we didn't check if symlink exists before deleting it, don't log
238 // if symlink doesn't exist.
239 if (errno != ENOENT)
240 PLOG(WARNING) << "Error deleting symlink " << symlink_path.value();
241 }
242
243 if (!create_symlink)
244 return base::PLATFORM_FILE_OK;
245
246 // Create new symlink to |dest_path|.
247 if (!file_util::CreateSymbolicLink(dest_path, symlink_path)) {
248 base::PlatformFileError error = SystemToPlatformError(errno);
249 PLOG(ERROR) << "Error creating symlink " << symlink_path.value()
250 << " for " << dest_path.value();
251 return error;
252 } else {
253 DVLOG(1) << "Created symlink " << symlink_path.value()
254 << " to " << dest_path.value();
255 }
256
257 return base::PLATFORM_FILE_OK;
258}
259
260// Deletes all files that match |path_to_delete_pattern| except for
261// |path_to_keep| on blocking pool.
262// If |path_to_keep| is empty, all files in |path_to_delete_pattern| are
263// deleted.
264void DeleteFilesSelectively(const FilePath& path_to_delete_pattern,
265 const FilePath& path_to_keep) {
266 // Enumerate all files in directory of |path_to_delete_pattern| that match
267 // base name of |path_to_delete_pattern|.
268 // If a file is not |path_to_keep|, delete it.
269 bool success = true;
270 file_util::FileEnumerator enumerator(
271 path_to_delete_pattern.DirName(),
272 false, // not recursive
273 static_cast<file_util::FileEnumerator::FileType>(
274 file_util::FileEnumerator::FILES |
275 file_util::FileEnumerator::SHOW_SYM_LINKS),
276 path_to_delete_pattern.BaseName().value());
277 for (FilePath current = enumerator.Next(); !current.empty();
278 current = enumerator.Next()) {
279 // If |path_to_keep| is not empty and same as current, don't delete it.
280 if (!path_to_keep.empty() && current == path_to_keep)
281 continue;
282
283 success = HANDLE_EINTR(unlink(current.value().c_str())) == 0;
284 if (!success)
285 DVLOG(1) << "Error deleting " << current.value();
286 else
287 DVLOG(1) << "Deleted " << current.value();
288 }
289}
290
[email protected]7986b8d2012-06-14 15:05:14291// Runs callback with pointers dereferenced.
292// Used to implement SetMountedStateOnUIThread.
[email protected]73f9c742012-06-15 07:37:13293void RunSetMountedStateCallback(const SetMountedStateCallback& callback,
294 base::PlatformFileError* error,
295 FilePath* cache_file_path) {
[email protected]7986b8d2012-06-14 15:05:14296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
297 DCHECK(error);
298 DCHECK(cache_file_path);
299
300 if (!callback.is_null())
301 callback.Run(*error, *cache_file_path);
302}
303
[email protected]73f9c742012-06-15 07:37:13304// Runs callback with pointers dereferenced.
305// Used to implement *OnUIThread methods.
306void RunCacheOperationCallback(const CacheOperationCallback& callback,
307 base::PlatformFileError* error,
308 const std::string& resource_id,
309 const std::string& md5) {
310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311 DCHECK(error);
312
313 if (!callback.is_null())
314 callback.Run(*error, resource_id, md5);
315}
316
[email protected]c960d222012-06-15 10:03:50317// Runs callback with pointers dereferenced.
318// Used to implement *OnUIThread methods.
319void RunGetFileFromCacheCallback(const GetFileFromCacheCallback& callback,
320 base::PlatformFileError* error,
321 const std::string& resource_id,
322 const std::string& md5,
323 FilePath* cache_file_path) {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
325 DCHECK(error);
326 DCHECK(cache_file_path);
327
328 if (!callback.is_null())
329 callback.Run(*error, resource_id, md5, *cache_file_path);
330}
331
[email protected]a321b9632012-06-14 03:29:17332} // namespace
[email protected]32a7fc852012-06-08 17:25:50333
[email protected]3653146a2012-05-29 13:41:47334std::string GDataCache::CacheEntry::ToString() const {
335 std::vector<std::string> cache_states;
336 if (GDataCache::IsCachePresent(cache_state))
337 cache_states.push_back("present");
338 if (GDataCache::IsCachePinned(cache_state))
339 cache_states.push_back("pinned");
340 if (GDataCache::IsCacheDirty(cache_state))
341 cache_states.push_back("dirty");
342
343 return base::StringPrintf("md5=%s, subdir=%s, cache_state=%s",
344 md5.c_str(),
345 CacheSubDirectoryTypeToString(sub_dir_type).c_str(),
346 JoinString(cache_states, ',').c_str());
347}
348
[email protected]fcc92a52012-06-08 22:54:16349GDataCache::GDataCache(
350 const FilePath& cache_root_path,
351 base::SequencedWorkerPool* pool,
352 const base::SequencedWorkerPool::SequenceToken& sequence_token)
[email protected]01ba15f72012-06-09 00:41:05353 : cache_root_path_(cache_root_path),
[email protected]6b70c7b2012-06-14 03:10:43354 cache_paths_(GetCachePaths(cache_root_path_)),
[email protected]01ba15f72012-06-09 00:41:05355 pool_(pool),
[email protected]73f9c742012-06-15 07:37:13356 sequence_token_(sequence_token),
357 ui_weak_ptr_factory_(this),
358 ui_weak_ptr_(ui_weak_ptr_factory_.GetWeakPtr()) {
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
[email protected]3653146a2012-05-29 13:41:47360}
361
362GDataCache::~GDataCache() {
[email protected]73f9c742012-06-15 07:37:13363 AssertOnSequencedWorkerPool();
[email protected]3653146a2012-05-29 13:41:47364}
365
[email protected]32a7fc852012-06-08 17:25:50366FilePath GDataCache::GetCacheDirectoryPath(
367 CacheSubDirectoryType sub_dir_type) const {
368 DCHECK_LE(0, sub_dir_type);
369 DCHECK_GT(NUM_CACHE_TYPES, sub_dir_type);
370 return cache_paths_[sub_dir_type];
371}
372
373FilePath GDataCache::GetCacheFilePath(const std::string& resource_id,
374 const std::string& md5,
375 CacheSubDirectoryType sub_dir_type,
376 CachedFileOrigin file_origin) const {
377 DCHECK(sub_dir_type != CACHE_TYPE_META);
378
379 // Runs on any thread.
380 // Filename is formatted as resource_id.md5, i.e. resource_id is the base
381 // name and md5 is the extension.
382 std::string base_name = util::EscapeCacheFileName(resource_id);
383 if (file_origin == CACHED_FILE_LOCALLY_MODIFIED) {
384 DCHECK(sub_dir_type == CACHE_TYPE_PERSISTENT);
385 base_name += FilePath::kExtensionSeparator;
386 base_name += kLocallyModifiedFileExtension;
387 } else if (!md5.empty()) {
388 base_name += FilePath::kExtensionSeparator;
389 base_name += util::EscapeCacheFileName(md5);
390 }
391 // For mounted archives the filename is formatted as resource_id.md5.mounted,
392 // i.e. resource_id.md5 is the base name and ".mounted" is the extension
393 if (file_origin == CACHED_FILE_MOUNTED) {
[email protected]a321b9632012-06-14 03:29:17394 DCHECK(sub_dir_type == CACHE_TYPE_PERSISTENT);
395 base_name += FilePath::kExtensionSeparator;
[email protected]ca5f6da2012-06-18 12:54:59396 base_name += util::kMountedArchiveFileExtension;
[email protected]32a7fc852012-06-08 17:25:50397 }
398 return GetCacheDirectoryPath(sub_dir_type).Append(base_name);
399}
400
[email protected]fcc92a52012-06-08 22:54:16401void GDataCache::AssertOnSequencedWorkerPool() {
402 DCHECK(!pool_ || pool_->IsRunningSequenceOnCurrentThread(sequence_token_));
403}
404
[email protected]01ba15f72012-06-09 00:41:05405bool GDataCache::IsUnderGDataCacheDirectory(const FilePath& path) const {
406 return cache_root_path_ == path || cache_root_path_.IsParent(path);
407}
408
[email protected]73f9c742012-06-15 07:37:13409void GDataCache::AddObserver(Observer* observer) {
410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411 observers_.AddObserver(observer);
412}
413
414void GDataCache::RemoveObserver(Observer* observer) {
415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
416 observers_.RemoveObserver(observer);
417}
418
[email protected]a321b9632012-06-14 03:29:17419void GDataCache::FreeDiskSpaceIfNeededFor(int64 num_bytes,
420 bool* has_enough_space) {
421 AssertOnSequencedWorkerPool();
422
423 // Do nothing and return if we have enough space.
424 *has_enough_space = HasEnoughSpaceFor(num_bytes);
425 if (*has_enough_space)
426 return;
427
428 // Otherwise, try to free up the disk space.
429 DVLOG(1) << "Freeing up disk space for " << num_bytes;
430 // First remove temporary files from the cache map.
[email protected]ca5f6da2012-06-18 12:54:59431 metadata_->RemoveTemporaryFiles();
[email protected]a321b9632012-06-14 03:29:17432 // Then remove all files under "tmp" directory.
433 RemoveAllFiles(GetCacheDirectoryPath(GDataCache::CACHE_TYPE_TMP));
434
435 // Check the disk space again.
436 *has_enough_space = HasEnoughSpaceFor(num_bytes);
437}
438
[email protected]c960d222012-06-15 10:03:50439void GDataCache::GetFileOnUIThread(const std::string& resource_id,
440 const std::string& md5,
441 const GetFileFromCacheCallback& callback) {
442 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
[email protected]a321b9632012-06-14 03:29:17443
[email protected]c960d222012-06-15 10:03:50444 base::PlatformFileError* error =
445 new base::PlatformFileError(base::PLATFORM_FILE_OK);
446 FilePath* cache_file_path = new FilePath;
447 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
448 FROM_HERE,
449 base::Bind(&GDataCache::GetFile,
450 base::Unretained(this),
451 resource_id,
452 md5,
453 error,
454 cache_file_path),
455 base::Bind(&RunGetFileFromCacheCallback,
456 callback,
457 base::Owned(error),
458 resource_id,
459 md5,
460 base::Owned(cache_file_path)));
[email protected]a321b9632012-06-14 03:29:17461}
462
[email protected]73f9c742012-06-15 07:37:13463void GDataCache::StoreOnUIThread(const std::string& resource_id,
464 const std::string& md5,
465 const FilePath& source_path,
466 FileOperationType file_operation_type,
467 const CacheOperationCallback& callback) {
468 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
469
470 base::PlatformFileError* error =
471 new base::PlatformFileError(base::PLATFORM_FILE_OK);
472 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
473 FROM_HERE,
474 base::Bind(&GDataCache::Store,
475 base::Unretained(this),
476 resource_id,
477 md5,
478 source_path,
479 file_operation_type,
480 error),
481 base::Bind(&RunCacheOperationCallback,
482 callback,
483 base::Owned(error),
484 resource_id,
485 md5));
486}
487
488void GDataCache::PinOnUIThread(const std::string& resource_id,
[email protected]44c0584e2012-06-15 23:55:41489 const std::string& md5,
490 const CacheOperationCallback& callback) {
[email protected]73f9c742012-06-15 07:37:13491 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
492
493 base::PlatformFileError* error =
494 new base::PlatformFileError(base::PLATFORM_FILE_OK);
495 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
496 FROM_HERE,
497 base::Bind(&GDataCache::Pin,
498 base::Unretained(this),
499 resource_id,
500 md5,
501 GDataCache::FILE_OPERATION_MOVE,
502 error),
503 base::Bind(&GDataCache::OnPinned,
504 ui_weak_ptr_,
505 base::Owned(error),
506 resource_id,
507 md5,
508 callback));
509}
510
511void GDataCache::UnpinOnUIThread(const std::string& resource_id,
512 const std::string& md5,
513 const CacheOperationCallback& callback) {
514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
515 base::PlatformFileError* error =
516 new base::PlatformFileError(base::PLATFORM_FILE_OK);
517 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
518 FROM_HERE,
519 base::Bind(&GDataCache::Unpin,
520 base::Unretained(this),
521 resource_id,
522 md5,
523 GDataCache::FILE_OPERATION_MOVE,
524 error),
525 base::Bind(&GDataCache::OnUnpinned,
526 ui_weak_ptr_,
527 base::Owned(error),
528 resource_id,
529 md5,
530 callback));
531}
532
533void GDataCache::SetMountedStateOnUIThread(
534 const FilePath& file_path,
535 bool to_mount,
536 const SetMountedStateCallback& callback) {
537 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
538
539 base::PlatformFileError* error =
540 new base::PlatformFileError(base::PLATFORM_FILE_OK);
541 FilePath* cache_file_path = new FilePath;
542 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
543 FROM_HERE,
544 base::Bind(&GDataCache::SetMountedState,
545 base::Unretained(this),
546 file_path,
547 to_mount,
548 error,
549 cache_file_path),
550 base::Bind(&RunSetMountedStateCallback,
551 callback,
552 base::Owned(error),
553 base::Owned(cache_file_path)));
554}
555
[email protected]c960d222012-06-15 10:03:50556void GDataCache::MarkDirtyOnUIThread(const std::string& resource_id,
557 const std::string& md5,
558 const GetFileFromCacheCallback& callback) {
559 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
[email protected]73f9c742012-06-15 07:37:13560
[email protected]c960d222012-06-15 10:03:50561 base::PlatformFileError* error =
562 new base::PlatformFileError(base::PLATFORM_FILE_OK);
563 FilePath* cache_file_path = new FilePath;
564 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
565 FROM_HERE,
566 base::Bind(&GDataCache::MarkDirty,
567 base::Unretained(this),
568 resource_id,
569 md5,
570 GDataCache::FILE_OPERATION_MOVE,
571 error,
572 cache_file_path),
573 base::Bind(&RunGetFileFromCacheCallback,
574 callback,
575 base::Owned(error),
576 resource_id,
577 md5,
578 base::Owned(cache_file_path)));
[email protected]73f9c742012-06-15 07:37:13579}
580
581void GDataCache::CommitDirtyOnUIThread(const std::string& resource_id,
582 const std::string& md5,
583 const CacheOperationCallback& callback) {
584 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
585
586 base::PlatformFileError* error =
587 new base::PlatformFileError(base::PLATFORM_FILE_OK);
588 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
589 FROM_HERE,
590 base::Bind(&GDataCache::CommitDirty,
591 base::Unretained(this),
592 resource_id,
593 md5,
594 GDataCache::FILE_OPERATION_MOVE,
595 error),
[email protected]d7664c22012-06-18 19:35:49596 base::Bind(&GDataCache::OnCommitDirty,
597 ui_weak_ptr_,
[email protected]73f9c742012-06-15 07:37:13598 base::Owned(error),
599 resource_id,
[email protected]d7664c22012-06-18 19:35:49600 md5,
601 callback));
[email protected]73f9c742012-06-15 07:37:13602}
603
604void GDataCache::ClearDirtyOnUIThread(const std::string& resource_id,
605 const std::string& md5,
606 const CacheOperationCallback& callback) {
607 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
608
609 base::PlatformFileError* error =
610 new base::PlatformFileError(base::PLATFORM_FILE_OK);
611 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
612 FROM_HERE,
613 base::Bind(&GDataCache::ClearDirty,
614 base::Unretained(this),
615 resource_id,
616 md5,
617 GDataCache::FILE_OPERATION_MOVE,
618 error),
619 base::Bind(&RunCacheOperationCallback,
620 callback,
621 base::Owned(error),
622 resource_id,
623 md5));
624}
625
626void GDataCache::RemoveOnUIThread(const std::string& resource_id,
627 const CacheOperationCallback& callback) {
628 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
629
630 base::PlatformFileError* error =
631 new base::PlatformFileError(base::PLATFORM_FILE_OK);
632
633 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
634 FROM_HERE,
635 base::Bind(&GDataCache::Remove,
636 base::Unretained(this),
637 resource_id,
638 error),
639 base::Bind(&RunCacheOperationCallback,
640 callback,
641 base::Owned(error),
642 resource_id,
643 "" /* md5 */));
644}
645
646void GDataCache::RequestInitializeOnUIThread() {
647 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
648
649 pool_->GetSequencedTaskRunner(sequence_token_)->PostTaskAndReply(
650 FROM_HERE,
651 base::Bind(&GDataCache::Initialize, base::Unretained(this)),
652 base::Bind(&GDataCache::OnInitialized, ui_weak_ptr_));
653}
654
[email protected]ca5f6da2012-06-18 12:54:59655scoped_ptr<GDataCache::CacheEntry> GDataCache::GetCacheEntry(
[email protected]73f9c742012-06-15 07:37:13656 const std::string& resource_id,
657 const std::string& md5) {
658 AssertOnSequencedWorkerPool();
[email protected]ca5f6da2012-06-18 12:54:59659 return metadata_->GetCacheEntry(resource_id, md5);
[email protected]73f9c742012-06-15 07:37:13660}
661
662// static
663GDataCache* GDataCache::CreateGDataCacheOnUIThread(
664 const FilePath& cache_root_path,
665 base::SequencedWorkerPool* pool,
666 const base::SequencedWorkerPool::SequenceToken& sequence_token) {
667 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
[email protected]ca5f6da2012-06-18 12:54:59668 return new GDataCache(cache_root_path, pool, sequence_token);
[email protected]73f9c742012-06-15 07:37:13669}
670
671void GDataCache::DestroyOnUIThread() {
672 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
673
674 // Invalidate the weak pointer.
675 ui_weak_ptr_factory_.InvalidateWeakPtrs();
676
677 // Destroy myself on the blocking pool.
678 pool_->GetSequencedTaskRunner(sequence_token_)->PostTask(
679 FROM_HERE,
680 base::Bind(&GDataCache::Destroy,
681 base::Unretained(this)));
682}
683
[email protected]ca5f6da2012-06-18 12:54:59684void GDataCache::Initialize() {
685 AssertOnSequencedWorkerPool();
686
687 GDataCacheMetadataMap* cache_data =
688 new GDataCacheMetadataMap(pool_, sequence_token_);
689 cache_data->Initialize(cache_paths_);
690 metadata_.reset(cache_data);
691}
692
[email protected]73f9c742012-06-15 07:37:13693void GDataCache::Destroy() {
694 AssertOnSequencedWorkerPool();
695 delete this;
696}
697
[email protected]c960d222012-06-15 10:03:50698void GDataCache::GetFile(const std::string& resource_id,
699 const std::string& md5,
700 base::PlatformFileError* error,
701 FilePath* cache_file_path) {
702 AssertOnSequencedWorkerPool();
703 DCHECK(error);
704 DCHECK(cache_file_path);
705
706 scoped_ptr<CacheEntry> cache_entry = GetCacheEntry(
707 resource_id, md5);
708 if (cache_entry.get() && cache_entry->IsPresent()) {
709 CachedFileOrigin file_origin;
710 if (cache_entry->IsMounted()) {
711 file_origin = CACHED_FILE_MOUNTED;
712 } else if (cache_entry->IsDirty()) {
713 file_origin = CACHED_FILE_LOCALLY_MODIFIED;
714 } else {
715 file_origin = CACHED_FILE_FROM_SERVER;
716 }
717 *cache_file_path = GetCacheFilePath(
718 resource_id,
719 md5,
720 cache_entry->sub_dir_type,
721 file_origin);
722 *error = base::PLATFORM_FILE_OK;
723 } else {
724 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
725 }
726}
727
[email protected]a321b9632012-06-14 03:29:17728void GDataCache::Store(const std::string& resource_id,
729 const std::string& md5,
730 const FilePath& source_path,
731 FileOperationType file_operation_type,
732 base::PlatformFileError* error) {
733 AssertOnSequencedWorkerPool();
734 DCHECK(error);
735
736 FilePath dest_path;
737 FilePath symlink_path;
738 int cache_state = CACHE_STATE_PRESENT;
739 CacheSubDirectoryType sub_dir_type = CACHE_TYPE_TMP;
740
741 scoped_ptr<CacheEntry> cache_entry = GetCacheEntry(resource_id, md5);
742
743 // If file was previously pinned, store it in persistent dir and create
744 // symlink in pinned dir.
745 if (cache_entry.get()) { // File exists in cache.
746 // If file is dirty or mounted, return error.
747 if (cache_entry->IsDirty() || cache_entry->IsMounted()) {
748 LOG(WARNING) << "Can't store a file to replace a "
749 << (cache_entry->IsDirty() ? "dirty" : "mounted")
750 << " file: res_id=" << resource_id
751 << ", md5=" << md5;
752 *error = base::PLATFORM_FILE_ERROR_IN_USE;
753 return;
754 }
755
756 cache_state |= cache_entry->cache_state;
757
758 // If file is pinned, determines destination path.
759 if (cache_entry->IsPinned()) {
760 sub_dir_type = CACHE_TYPE_PERSISTENT;
761 dest_path = GetCacheFilePath(resource_id, md5, sub_dir_type,
762 CACHED_FILE_FROM_SERVER);
763 symlink_path = GetCacheFilePath(
764 resource_id, std::string(), CACHE_TYPE_PINNED,
765 CACHED_FILE_FROM_SERVER);
766 }
767 }
768
769 // File wasn't pinned or doesn't exist in cache, store in tmp dir.
770 if (dest_path.empty()) {
771 DCHECK_EQ(CACHE_TYPE_TMP, sub_dir_type);
772 dest_path = GetCacheFilePath(resource_id, md5, sub_dir_type,
773 CACHED_FILE_FROM_SERVER);
774 }
775
776 *error = ModifyCacheState(
777 source_path,
778 dest_path,
779 file_operation_type,
780 symlink_path,
781 !symlink_path.empty()); // create symlink
782
783 // Determine search pattern for stale filenames corrresponding to resource_id,
784 // either "<resource_id>*" or "<resource_id>.*".
785 FilePath stale_filenames_pattern;
786 if (md5.empty()) {
787 // No md5 means no extension, append '*' after base name, i.e.
788 // "<resource_id>*".
789 // Cannot call |dest_path|.ReplaceExtension when there's no md5 extension:
790 // if base name of |dest_path| (i.e. escaped resource_id) contains the
791 // extension separator '.', ReplaceExtension will remove it and everything
792 // after it. The result will be nothing like the escaped resource_id.
[email protected]ca5f6da2012-06-18 12:54:59793 stale_filenames_pattern = FilePath(dest_path.value() + util::kWildCard);
[email protected]a321b9632012-06-14 03:29:17794 } else {
795 // Replace md5 extension with '*' i.e. "<resource_id>.*".
796 // Note that ReplaceExtension automatically prefixes the extension with the
797 // extension separator '.'.
[email protected]ca5f6da2012-06-18 12:54:59798 stale_filenames_pattern = dest_path.ReplaceExtension(util::kWildCard);
[email protected]a321b9632012-06-14 03:29:17799 }
800
801 // Delete files that match |stale_filenames_pattern| except for |dest_path|.
802 DeleteFilesSelectively(stale_filenames_pattern, dest_path);
803
804 if (*error == base::PLATFORM_FILE_OK) {
805 // Now that file operations have completed, update cache map.
[email protected]ca5f6da2012-06-18 12:54:59806 metadata_->UpdateCache(resource_id, md5, sub_dir_type, cache_state);
[email protected]a321b9632012-06-14 03:29:17807 }
808}
809
810void GDataCache::Pin(const std::string& resource_id,
811 const std::string& md5,
812 FileOperationType file_operation_type,
813 base::PlatformFileError* error) {
814 AssertOnSequencedWorkerPool();
815 DCHECK(error);
816
817 FilePath source_path;
818 FilePath dest_path;
819 FilePath symlink_path;
820 bool create_symlink = true;
821 int cache_state = CACHE_STATE_PINNED;
822 CacheSubDirectoryType sub_dir_type = CACHE_TYPE_PERSISTENT;
823
824 scoped_ptr<CacheEntry> cache_entry = GetCacheEntry(resource_id, md5);
825
826 if (!cache_entry.get()) { // Entry does not exist in cache.
827 // Set both |dest_path| and |source_path| to /dev/null, so that:
828 // 1) ModifyCacheState won't move files when |source_path| and |dest_path|
829 // are the same.
830 // 2) symlinks to /dev/null will be picked up by GDataSyncClient to download
831 // pinned files that don't exist in cache.
832 dest_path = FilePath(kSymLinkToDevNull);
833 source_path = dest_path;
834
835 // Set sub_dir_type to PINNED to indicate that the file doesn't exist.
836 // When the file is finally downloaded and StoreToCache called, it will be
837 // moved to persistent directory.
838 sub_dir_type = CACHE_TYPE_PINNED;
839 } else { // File exists in cache, determines destination path.
840 cache_state |= cache_entry->cache_state;
841
842 // Determine source and destination paths.
843
844 // If file is dirty or mounted, don't move it, so determine |dest_path| and
845 // set |source_path| the same, because ModifyCacheState only moves files if
846 // source and destination are different.
847 if (cache_entry->IsDirty() || cache_entry->IsMounted()) {
848 DCHECK_EQ(CACHE_TYPE_PERSISTENT, cache_entry->sub_dir_type);
849 dest_path = GetCacheFilePath(resource_id,
850 md5,
851 cache_entry->sub_dir_type,
852 CACHED_FILE_LOCALLY_MODIFIED);
853 source_path = dest_path;
854 } else {
855 // Gets the current path of the file in cache.
856 source_path = GetCacheFilePath(resource_id,
857 md5,
858 cache_entry->sub_dir_type,
859 CACHED_FILE_FROM_SERVER);
860
861 // If file was pinned before but actual file blob doesn't exist in cache:
862 // - don't need to move the file, so set |dest_path| to |source_path|,
863 // because ModifyCacheState only moves files if source and destination
864 // are different
865 // - don't create symlink since it already exists.
866 if (cache_entry->sub_dir_type == CACHE_TYPE_PINNED) {
867 dest_path = source_path;
868 create_symlink = false;
869 } else { // File exists, move it to persistent dir.
870 dest_path = GetCacheFilePath(resource_id,
871 md5,
872 CACHE_TYPE_PERSISTENT,
873 CACHED_FILE_FROM_SERVER);
874 }
875 }
876 }
877
878 // Create symlink in pinned dir.
879 if (create_symlink) {
880 symlink_path = GetCacheFilePath(resource_id,
881 std::string(),
882 CACHE_TYPE_PINNED,
883 CACHED_FILE_FROM_SERVER);
884 }
885
886 *error = ModifyCacheState(source_path,
887 dest_path,
888 file_operation_type,
889 symlink_path,
890 create_symlink);
891
892 if (*error == base::PLATFORM_FILE_OK) {
893 // Now that file operations have completed, update cache map.
[email protected]ca5f6da2012-06-18 12:54:59894 metadata_->UpdateCache(resource_id, md5, sub_dir_type, cache_state);
[email protected]a321b9632012-06-14 03:29:17895 }
896}
897
898void GDataCache::Unpin(const std::string& resource_id,
899 const std::string& md5,
900 FileOperationType file_operation_type,
901 base::PlatformFileError* error) {
902 AssertOnSequencedWorkerPool();
903 DCHECK(error);
904
905 scoped_ptr<CacheEntry> cache_entry = GetCacheEntry(resource_id, md5);
906
907 // Unpinning a file means its entry must exist in cache.
908 if (!cache_entry.get()) {
909 LOG(WARNING) << "Can't unpin a file that wasn't pinned or cached: res_id="
910 << resource_id
911 << ", md5=" << md5;
912 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
913 return;
914 }
915
916 // Entry exists in cache, determines source and destination paths.
917
918 FilePath source_path;
919 FilePath dest_path;
920 CacheSubDirectoryType sub_dir_type = CACHE_TYPE_TMP;
921
922 // If file is dirty or mounted, don't move it, so determine |dest_path| and
923 // set |source_path| the same, because ModifyCacheState moves files if source
924 // and destination are different.
925 if (cache_entry->IsDirty() || cache_entry->IsMounted()) {
926 sub_dir_type = CACHE_TYPE_PERSISTENT;
927 DCHECK_EQ(sub_dir_type, cache_entry->sub_dir_type);
928 dest_path = GetCacheFilePath(resource_id,
929 md5,
930 cache_entry->sub_dir_type,
931 CACHED_FILE_LOCALLY_MODIFIED);
932 source_path = dest_path;
933 } else {
934 // Gets the current path of the file in cache.
935 source_path = GetCacheFilePath(resource_id,
936 md5,
937 cache_entry->sub_dir_type,
938 CACHED_FILE_FROM_SERVER);
939
940 // If file was pinned but actual file blob still doesn't exist in cache,
941 // don't need to move the file, so set |dest_path| to |source_path|, because
942 // ModifyCacheState only moves files if source and destination are
943 // different.
944 if (cache_entry->sub_dir_type == CACHE_TYPE_PINNED) {
945 dest_path = source_path;
946 } else { // File exists, move it to tmp dir.
947 dest_path = GetCacheFilePath(resource_id, md5,
948 CACHE_TYPE_TMP,
949 CACHED_FILE_FROM_SERVER);
950 }
951 }
952
953 // If file was pinned, get absolute path of symlink in pinned dir so as to
954 // remove it.
955 FilePath symlink_path;
956 if (cache_entry->IsPinned()) {
957 symlink_path = GetCacheFilePath(resource_id,
958 std::string(),
959 CACHE_TYPE_PINNED,
960 CACHED_FILE_FROM_SERVER);
961 }
962
963 *error = ModifyCacheState(
964 source_path,
965 dest_path,
966 file_operation_type,
967 symlink_path, // This will be deleted if it exists.
968 false /* don't create symlink*/);
969
970 if (*error == base::PLATFORM_FILE_OK) {
971 // Now that file operations have completed, update cache map.
972 int cache_state = ClearCachePinned(cache_entry->cache_state);
[email protected]ca5f6da2012-06-18 12:54:59973 metadata_->UpdateCache(resource_id, md5, sub_dir_type, cache_state);
[email protected]a321b9632012-06-14 03:29:17974 }
975}
976
977void GDataCache::SetMountedState(const FilePath& file_path,
978 bool to_mount,
979 base::PlatformFileError *error,
980 FilePath* cache_file_path) {
981 AssertOnSequencedWorkerPool();
982 DCHECK(error);
983 DCHECK(cache_file_path);
984
985 // Parse file path to obtain resource_id, md5 and extra_extension.
986 std::string resource_id;
987 std::string md5;
988 std::string extra_extension;
989 util::ParseCacheFilePath(file_path, &resource_id, &md5, &extra_extension);
990 // The extra_extension shall be ".mounted" iff we're unmounting.
[email protected]ca5f6da2012-06-18 12:54:59991 DCHECK(!to_mount == (extra_extension == util::kMountedArchiveFileExtension));
[email protected]a321b9632012-06-14 03:29:17992
993 // Get cache entry associated with the resource_id and md5
994 scoped_ptr<CacheEntry> cache_entry = GetCacheEntry(
995 resource_id, md5);
996 if (!cache_entry.get()) {
997 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
998 return;
999 }
1000 if (to_mount == cache_entry->IsMounted()) {
1001 *error = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
1002 return;
1003 }
1004
1005 // Get the subdir type and path for the unmounted state.
1006 CacheSubDirectoryType unmounted_subdir =
1007 cache_entry->IsPinned() ? CACHE_TYPE_PERSISTENT : CACHE_TYPE_TMP;
1008 FilePath unmounted_path = GetCacheFilePath(
1009 resource_id, md5, unmounted_subdir, CACHED_FILE_FROM_SERVER);
1010
1011 // Get the subdir type and path for the mounted state.
1012 CacheSubDirectoryType mounted_subdir = CACHE_TYPE_PERSISTENT;
1013 FilePath mounted_path = GetCacheFilePath(
1014 resource_id, md5, mounted_subdir, CACHED_FILE_MOUNTED);
1015
1016 // Determine the source and destination paths for moving the cache blob.
1017 FilePath source_path;
1018 CacheSubDirectoryType dest_subdir;
1019 int cache_state = cache_entry->cache_state;
1020 if (to_mount) {
1021 source_path = unmounted_path;
1022 *cache_file_path = mounted_path;
1023 dest_subdir = mounted_subdir;
1024 cache_state = SetCacheMounted(cache_state);
1025 } else {
1026 source_path = mounted_path;
1027 *cache_file_path = unmounted_path;
1028 dest_subdir = unmounted_subdir;
1029 cache_state = ClearCacheMounted(cache_state);
1030 }
1031
1032 // Move cache blob from source path to destination path.
1033 *error = ModifyCacheState(source_path, *cache_file_path,
1034 FILE_OPERATION_MOVE, FilePath(), false);
1035 if (*error == base::PLATFORM_FILE_OK) {
1036 // Now that cache operation is complete, update cache map
[email protected]ca5f6da2012-06-18 12:54:591037 metadata_->UpdateCache(resource_id, md5, dest_subdir, cache_state);
[email protected]a321b9632012-06-14 03:29:171038 }
1039}
1040
[email protected]c960d222012-06-15 10:03:501041void GDataCache::MarkDirty(const std::string& resource_id,
1042 const std::string& md5,
1043 FileOperationType file_operation_type,
1044 base::PlatformFileError* error,
1045 FilePath* cache_file_path) {
1046 AssertOnSequencedWorkerPool();
1047 DCHECK(error);
1048 DCHECK(cache_file_path);
1049
1050 // If file has already been marked dirty in previous instance of chrome, we
1051 // would have lost the md5 info during cache initialization, because the file
1052 // would have been renamed to .local extension.
1053 // So, search for entry in cache without comparing md5.
1054 scoped_ptr<CacheEntry> cache_entry =
1055 GetCacheEntry(resource_id, std::string());
1056
1057 // Marking a file dirty means its entry and actual file blob must exist in
1058 // cache.
1059 if (!cache_entry.get() ||
1060 cache_entry->sub_dir_type == CACHE_TYPE_PINNED) {
1061 LOG(WARNING) << "Can't mark dirty a file that wasn't cached: res_id="
1062 << resource_id
1063 << ", md5=" << md5;
1064 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
1065 return;
1066 }
1067
1068 // If a file is already dirty (i.e. MarkDirtyInCache was called before),
1069 // delete outgoing symlink if it exists.
1070 // TODO(benchan): We should only delete outgoing symlink if file is currently
1071 // not being uploaded. However, for now, cache doesn't know if uploading of a
1072 // file is in progress. Per zel, the upload process should be canceled before
1073 // MarkDirtyInCache is called again.
1074 if (cache_entry->IsDirty()) {
1075 // The file must be in persistent dir.
1076 DCHECK_EQ(CACHE_TYPE_PERSISTENT, cache_entry->sub_dir_type);
1077
1078 // Determine symlink path in outgoing dir, so as to remove it.
1079 FilePath symlink_path = GetCacheFilePath(
1080 resource_id,
1081 std::string(),
1082 CACHE_TYPE_OUTGOING,
1083 CACHED_FILE_FROM_SERVER);
1084
1085 // We're not moving files here, so simply use empty FilePath for both
1086 // |source_path| and |dest_path| because ModifyCacheState only move files
1087 // if source and destination are different.
1088 *error = ModifyCacheState(
1089 FilePath(), // non-applicable source path
1090 FilePath(), // non-applicable dest path
1091 file_operation_type,
1092 symlink_path,
1093 false /* don't create symlink */);
1094
1095 // Determine current path of dirty file.
1096 if (*error == base::PLATFORM_FILE_OK) {
1097 *cache_file_path = GetCacheFilePath(
1098 resource_id,
1099 md5,
1100 CACHE_TYPE_PERSISTENT,
1101 CACHED_FILE_LOCALLY_MODIFIED);
1102 }
1103 return;
1104 }
1105
1106 // Move file to persistent dir with new .local extension.
1107
1108 // Get the current path of the file in cache.
1109 FilePath source_path = GetCacheFilePath(
1110 resource_id,
1111 md5,
1112 cache_entry->sub_dir_type,
1113 CACHED_FILE_FROM_SERVER);
1114
1115 // Determine destination path.
1116 CacheSubDirectoryType sub_dir_type = CACHE_TYPE_PERSISTENT;
1117 *cache_file_path = GetCacheFilePath(resource_id,
1118 md5,
1119 sub_dir_type,
1120 CACHED_FILE_LOCALLY_MODIFIED);
1121
1122 // If file is pinned, update symlink in pinned dir.
1123 FilePath symlink_path;
1124 if (cache_entry->IsPinned()) {
1125 symlink_path = GetCacheFilePath(resource_id,
1126 std::string(),
1127 CACHE_TYPE_PINNED,
1128 CACHED_FILE_FROM_SERVER);
1129 }
1130
1131 *error = ModifyCacheState(
1132 source_path,
1133 *cache_file_path,
1134 file_operation_type,
1135 symlink_path,
1136 !symlink_path.empty() /* create symlink */);
1137
1138 if (*error == base::PLATFORM_FILE_OK) {
1139 // Now that file operations have completed, update cache map.
1140 int cache_state = SetCacheDirty(cache_entry->cache_state);
[email protected]ca5f6da2012-06-18 12:54:591141 metadata_->UpdateCache(resource_id, md5, sub_dir_type, cache_state);
[email protected]c960d222012-06-15 10:03:501142 }
1143}
1144
[email protected]a321b9632012-06-14 03:29:171145void GDataCache::CommitDirty(const std::string& resource_id,
1146 const std::string& md5,
1147 FileOperationType file_operation_type,
1148 base::PlatformFileError* error) {
1149 AssertOnSequencedWorkerPool();
1150 DCHECK(error);
1151
1152 // If file has already been marked dirty in previous instance of chrome, we
1153 // would have lost the md5 info during cache initialization, because the file
1154 // would have been renamed to .local extension.
1155 // So, search for entry in cache without comparing md5.
1156 scoped_ptr<CacheEntry> cache_entry =
1157 GetCacheEntry(resource_id, std::string());
1158
1159 // Committing a file dirty means its entry and actual file blob must exist in
1160 // cache.
1161 if (!cache_entry.get() ||
1162 cache_entry->sub_dir_type == CACHE_TYPE_PINNED) {
1163 LOG(WARNING) << "Can't commit dirty a file that wasn't cached: res_id="
1164 << resource_id
1165 << ", md5=" << md5;
1166 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
1167 return;
1168 }
1169
1170 // If a file is not dirty (it should have been marked dirty via
1171 // MarkDirtyInCache), committing it dirty is an invalid operation.
1172 if (!cache_entry->IsDirty()) {
1173 LOG(WARNING) << "Can't commit a non-dirty file: res_id="
1174 << resource_id
1175 << ", md5=" << md5;
1176 *error = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
1177 return;
1178 }
1179
1180 // Dirty files must be in persistent dir.
1181 DCHECK_EQ(CACHE_TYPE_PERSISTENT, cache_entry->sub_dir_type);
1182
1183 // Create symlink in outgoing dir.
1184 FilePath symlink_path = GetCacheFilePath(resource_id,
1185 std::string(),
1186 CACHE_TYPE_OUTGOING,
1187 CACHED_FILE_FROM_SERVER);
1188
1189 // Get target path of symlink i.e. current path of the file in cache.
1190 FilePath target_path = GetCacheFilePath(resource_id,
1191 md5,
1192 cache_entry->sub_dir_type,
1193 CACHED_FILE_LOCALLY_MODIFIED);
1194
1195 // Since there's no need to move files, use |target_path| for both
1196 // |source_path| and |dest_path|, because ModifyCacheState only moves files
1197 // if source and destination are different.
1198 *error = ModifyCacheState(target_path, // source
1199 target_path, // destination
1200 file_operation_type,
1201 symlink_path,
1202 true /* create symlink */);
1203}
1204
1205void GDataCache::ClearDirty(const std::string& resource_id,
1206 const std::string& md5,
1207 FileOperationType file_operation_type,
1208 base::PlatformFileError* error) {
1209 AssertOnSequencedWorkerPool();
1210 DCHECK(error);
1211
1212 // |md5| is the new .<md5> extension to rename the file to.
1213 // So, search for entry in cache without comparing md5.
1214 scoped_ptr<CacheEntry> cache_entry =
1215 GetCacheEntry(resource_id, std::string());
1216
1217 // Clearing a dirty file means its entry and actual file blob must exist in
1218 // cache.
1219 if (!cache_entry.get() ||
1220 cache_entry->sub_dir_type == CACHE_TYPE_PINNED) {
1221 LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
1222 << "res_id=" << resource_id
1223 << ", md5=" << md5;
1224 *error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
1225 return;
1226 }
1227
1228 // If a file is not dirty (it should have been marked dirty via
1229 // MarkDirtyInCache), clearing its dirty state is an invalid operation.
1230 if (!cache_entry->IsDirty()) {
1231 LOG(WARNING) << "Can't clear dirty state of a non-dirty file: res_id="
1232 << resource_id
1233 << ", md5=" << md5;
1234 *error = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
1235 return;
1236 }
1237
1238 // File must be dirty and hence in persistent dir.
1239 DCHECK_EQ(CACHE_TYPE_PERSISTENT, cache_entry->sub_dir_type);
1240
1241 // Get the current path of the file in cache.
1242 FilePath source_path = GetCacheFilePath(resource_id,
1243 md5,
1244 cache_entry->sub_dir_type,
1245 CACHED_FILE_LOCALLY_MODIFIED);
1246
1247 // Determine destination path.
1248 // If file is pinned, move it to persistent dir with .md5 extension;
1249 // otherwise, move it to tmp dir with .md5 extension.
1250 CacheSubDirectoryType sub_dir_type =
1251 cache_entry->IsPinned() ? CACHE_TYPE_PERSISTENT : CACHE_TYPE_TMP;
1252 FilePath dest_path = GetCacheFilePath(resource_id,
1253 md5,
1254 sub_dir_type,
1255 CACHED_FILE_FROM_SERVER);
1256
1257 // Delete symlink in outgoing dir.
1258 FilePath symlink_path = GetCacheFilePath(resource_id,
1259 std::string(),
1260 CACHE_TYPE_OUTGOING,
1261 CACHED_FILE_FROM_SERVER);
1262
1263 *error = ModifyCacheState(source_path,
1264 dest_path,
1265 file_operation_type,
1266 symlink_path,
1267 false /* don't create symlink */);
1268
1269 // If file is pinned, update symlink in pinned dir.
1270 if (*error == base::PLATFORM_FILE_OK && cache_entry->IsPinned()) {
1271 symlink_path = GetCacheFilePath(resource_id,
1272 std::string(),
1273 CACHE_TYPE_PINNED,
1274 CACHED_FILE_FROM_SERVER);
1275
1276 // Since there's no moving of files here, use |dest_path| for both
1277 // |source_path| and |dest_path|, because ModifyCacheState only moves files
1278 // if source and destination are different.
1279 *error = ModifyCacheState(dest_path, // source path
1280 dest_path, // destination path
1281 file_operation_type,
1282 symlink_path,
1283 true /* create symlink */);
1284 }
1285
1286 if (*error == base::PLATFORM_FILE_OK) {
1287 // Now that file operations have completed, update cache map.
1288 int cache_state = ClearCacheDirty(cache_entry->cache_state);
[email protected]ca5f6da2012-06-18 12:54:591289 metadata_->UpdateCache(resource_id, md5, sub_dir_type, cache_state);
[email protected]a321b9632012-06-14 03:29:171290 }
1291}
1292
1293void GDataCache::Remove(const std::string& resource_id,
1294 base::PlatformFileError* error) {
1295 AssertOnSequencedWorkerPool();
1296 DCHECK(error);
1297
1298 // MD5 is not passed into RemoveFromCache and hence
1299 // RemoveFromCacheOnBlockingPool, because we would delete all cache files
1300 // corresponding to <resource_id> regardless of the md5.
1301 // So, search for entry in cache without taking md5 into account.
1302 scoped_ptr<CacheEntry> cache_entry =
1303 GetCacheEntry(resource_id, std::string());
1304
1305 // If entry doesn't exist or is dirty or mounted in cache, nothing to do.
1306 if (!cache_entry.get() ||
1307 cache_entry->IsDirty() ||
1308 cache_entry->IsMounted()) {
1309 DVLOG(1) << "Entry is "
1310 << (cache_entry.get() ?
1311 (cache_entry->IsDirty() ? "dirty" : "mounted") :
1312 "non-existent")
1313 << " in cache, not removing";
1314 *error = base::PLATFORM_FILE_OK;
1315 return;
1316 }
1317
1318 // Determine paths to delete all cache versions of |resource_id| in
1319 // persistent, tmp and pinned directories.
1320 std::vector<FilePath> paths_to_delete;
1321
1322 // For files in persistent and tmp dirs, delete files that match
1323 // "<resource_id>.*".
1324 paths_to_delete.push_back(GetCacheFilePath(resource_id,
[email protected]ca5f6da2012-06-18 12:54:591325 util::kWildCard,
[email protected]a321b9632012-06-14 03:29:171326 CACHE_TYPE_PERSISTENT,
1327 CACHED_FILE_FROM_SERVER));
1328 paths_to_delete.push_back(GetCacheFilePath(resource_id,
[email protected]ca5f6da2012-06-18 12:54:591329 util::kWildCard,
[email protected]a321b9632012-06-14 03:29:171330 CACHE_TYPE_TMP,
1331 CACHED_FILE_FROM_SERVER));
1332
1333 // For pinned files, filename is "<resource_id>" with no extension, so delete
1334 // "<resource_id>".
1335 paths_to_delete.push_back(GetCacheFilePath(resource_id,
1336 std::string(),
1337 CACHE_TYPE_PINNED,
1338 CACHED_FILE_FROM_SERVER));
1339
1340 // Don't delete locally modified (i.e. dirty and possibly outgoing) files.
1341 // Since we're not deleting outgoing symlinks, we don't need to append
1342 // outgoing path to |paths_to_delete|.
1343 FilePath path_to_keep = GetCacheFilePath(resource_id,
1344 std::string(),
1345 CACHE_TYPE_PERSISTENT,
1346 CACHED_FILE_LOCALLY_MODIFIED);
1347
1348 for (size_t i = 0; i < paths_to_delete.size(); ++i) {
1349 DeleteFilesSelectively(paths_to_delete[i], path_to_keep);
1350 }
1351
1352 // Now that all file operations have completed, remove from cache map.
[email protected]ca5f6da2012-06-18 12:54:591353 metadata_->RemoveFromCache(resource_id);
[email protected]a321b9632012-06-14 03:29:171354
1355 *error = base::PLATFORM_FILE_OK;
1356}
1357
[email protected]73f9c742012-06-15 07:37:131358void GDataCache::OnInitialized() {
1359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1360 FOR_EACH_OBSERVER(Observer, observers_, OnCacheInitialized());
[email protected]3653146a2012-05-29 13:41:471361}
1362
[email protected]73f9c742012-06-15 07:37:131363void GDataCache::OnPinned(base::PlatformFileError* error,
1364 const std::string& resource_id,
1365 const std::string& md5,
1366 const CacheOperationCallback& callback) {
1367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1368 DCHECK(error);
1369
1370 if (!callback.is_null())
1371 callback.Run(*error, resource_id, md5);
1372
1373 if (*error == base::PLATFORM_FILE_OK)
1374 FOR_EACH_OBSERVER(Observer, observers_, OnCachePinned(resource_id, md5));
[email protected]3653146a2012-05-29 13:41:471375}
1376
[email protected]73f9c742012-06-15 07:37:131377void GDataCache::OnUnpinned(base::PlatformFileError* error,
1378 const std::string& resource_id,
1379 const std::string& md5,
1380 const CacheOperationCallback& callback) {
1381 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1382 DCHECK(error);
[email protected]a321b9632012-06-14 03:29:171383
[email protected]73f9c742012-06-15 07:37:131384 if (!callback.is_null())
1385 callback.Run(*error, resource_id, md5);
[email protected]a321b9632012-06-14 03:29:171386
[email protected]73f9c742012-06-15 07:37:131387 if (*error == base::PLATFORM_FILE_OK)
1388 FOR_EACH_OBSERVER(Observer, observers_, OnCacheUnpinned(resource_id, md5));
[email protected]a321b9632012-06-14 03:29:171389
[email protected]73f9c742012-06-15 07:37:131390 // Now the file is moved from "persistent" to "tmp" directory.
1391 // It's a chance to free up space if needed.
1392 bool* has_enough_space = new bool(false);
1393 pool_->GetSequencedTaskRunner(sequence_token_)->PostTask(
1394 FROM_HERE,
1395 base::Bind(&GDataCache::FreeDiskSpaceIfNeededFor,
1396 base::Unretained(this),
1397 0,
1398 base::Owned(has_enough_space)));
[email protected]3653146a2012-05-29 13:41:471399}
1400
[email protected]d7664c22012-06-18 19:35:491401void GDataCache::OnCommitDirty(base::PlatformFileError* error,
1402 const std::string& resource_id,
1403 const std::string& md5,
1404 const CacheOperationCallback& callback) {
1405 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1406 DCHECK(error);
1407
1408 if (!callback.is_null())
1409 callback.Run(*error, resource_id, md5);
1410
1411 if (*error == base::PLATFORM_FILE_OK)
1412 FOR_EACH_OBSERVER(Observer, observers_, OnCacheCommitted(resource_id));
1413}
1414
[email protected]01ba15f72012-06-09 00:41:051415// static
1416FilePath GDataCache::GetCacheRootPath(Profile* profile) {
1417 FilePath cache_base_path;
1418 chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path);
1419 FilePath cache_root_path =
1420 cache_base_path.Append(chrome::kGDataCacheDirname);
1421 return cache_root_path.Append(kGDataCacheVersionDir);
1422}
1423
[email protected]a321b9632012-06-14 03:29:171424void SetFreeDiskSpaceGetterForTesting(FreeDiskSpaceGetterInterface* getter) {
1425 delete global_free_disk_getter_for_testing; // Safe to delete NULL;
1426 global_free_disk_getter_for_testing = getter;
1427}
1428
[email protected]3653146a2012-05-29 13:41:471429} // namespace gdata