diff options
author | satorux@chromium.org <satorux@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-10 17:33:29 +0000 |
---|---|---|
committer | satorux@chromium.org <satorux@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-10 17:33:29 +0000 |
commit | 6d32cb06edeb614f99568027e06a32c13c6eb6bb (patch) | |
tree | 29edbd4f53858cdfe234028d6f7a7da0211436d8 | |
parent | 2cd60b80267486f4fcf362b13940a34217d2dfe3 (diff) | |
download | chromium_src-6d32cb06edeb614f99568027e06a32c13c6eb6bb.zip chromium_src-6d32cb06edeb614f99568027e06a32c13c6eb6bb.tar.gz chromium_src-6d32cb06edeb614f99568027e06a32c13c6eb6bb.tar.bz2 |
gdata: Implement very simple cache eviction logic
The basic idea is to evict files from <gcache>/tmp as needed, but
do not remove recursively as we don't want to touch <gache>/tmp/downloads,
which is used for user initiated downloads like "Save As"
Here's the behaviors added in this patch.
Before downloading a file:
- Check if we have enough space, based on the expected file size.
- If we don't have enough space, remove files in <gcache>/tmp
- If we still don't have enough space, return base::PLATFORM_FILE_ERROR_NO_SPACE
- If we have enough space, start downloading the file
After a file is downloaded:
- At this point, the disk can be full or nearly full for various reasons.
- If we don't have enough buffer space,
- Remove the downloaded file
- Remove files in <gcache>/tmp
- Return base::PLATFORM_FILE_ERROR_NO_SPACE
After a file is unpinned:
- This is a chance to remove files in <gcache>/tmp
- Try to remove files in <gcache>/tmp if we don't have enough space
BUG=chromium-os:28553
TEST=added tests.
Review URL: https://chromiumcodereview.appspot.com/10031001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@131578 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_file_system.cc | 196 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_file_system.h | 45 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc | 190 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_files.cc | 14 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_files.h | 3 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_files_unittest.cc | 51 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 |
7 files changed, 490 insertions, 10 deletions
diff --git a/chrome/browser/chromeos/gdata/gdata_file_system.cc b/chrome/browser/chromeos/gdata/gdata_file_system.cc index 0213f86..a06a7a2 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system.cc +++ b/chrome/browser/chromeos/gdata/gdata_file_system.cc @@ -9,6 +9,7 @@ #include <utility> #include "base/bind.h" +#include "base/chromeos/chromeos_version.h" #include "base/eintr_wrapper.h" #include "base/file_util.h" #include "base/json/json_file_value_serializer.h" @@ -18,8 +19,10 @@ #include "base/message_loop_proxy.h" #include "base/metrics/histogram.h" #include "base/platform_file.h" +#include "base/sys_info.h" #include "base/threading/platform_thread.h" #include "base/threading/sequenced_worker_pool.h" +#include "base/threading/thread_restrictions.h" #include "base/synchronization/waitable_event.h" #include "base/values.h" #include "chrome/browser/chromeos/gdata/gdata_documents_service.h" @@ -71,6 +74,58 @@ const FilePath::CharType kAccountMetadataFile[] = FILE_PATH_LITERAL("account_metadata.json"); const FilePath::CharType kSymLinkToDevNull[] = FILE_PATH_LITERAL("/dev/null"); +// Returns the home directory path, or an empty string if the home directory +// is not found. +// Copied from webkit/chromeos/cros_mount_point_provider.h. +// TODO(satorux): Share the code. +std::string GetHomeDirectory() { + if (base::chromeos::IsRunningOnChromeOS()) + return "/home/chronos/user"; + + const char* home = getenv("HOME"); + if (home) + return home; + return ""; +} + +// Used to tweak GetAmountOfFreeDiskSpace() behavior for testing. +FreeDiskSpaceGetterInterface* global_free_disk_getter_for_testing = NULL; + +// Gets the amount of free disk space. Use +// |global_free_disk_getter_for_testing| if set. +int64 GetAmountOfFreeDiskSpace() { + if (global_free_disk_getter_for_testing) + return global_free_disk_getter_for_testing->AmountOfFreeDiskSpace(); + + return base::SysInfo::AmountOfFreeDiskSpace( + FilePath::FromUTF8Unsafe(GetHomeDirectory())); +} + +// Returns true if we have sufficient space to store the given number of +// bytes, while keeping kMinFreeSpace bytes on the disk. +bool HasEnoughSpaceFor(int64 num_bytes) { + int64 free_space = GetAmountOfFreeDiskSpace(); + // Substract this as if this portion does not exist. + free_space -= kMinFreeSpace; + return (free_space >= num_bytes); +} + +// Remove all files under the given directory, non-recursively. +// Do not remove recursively as we don't want to touch <gache>/tmp/downloads, +// which is used for user initiated downloads like "Save As" +void RemoveAllFiles(const FilePath& directory) { + using file_util::FileEnumerator; + + FileEnumerator enumerator(directory, false /* recursive */, + FileEnumerator::FILES); + for (FilePath file_path = enumerator.Next(); !file_path.empty(); + file_path = enumerator.Next()) { + DVLOG(1) << "Removing " << file_path.value(); + if (!file_util::Delete(file_path, false /* recursive */)) + LOG(WARNING) << "Failed to delete " << file_path.value(); + } +} + // Converts gdata error code into file platform error code. base::PlatformFileError GDataToPlatformError(GDataErrorCode status) { switch (status) { @@ -1492,7 +1547,77 @@ void GDataFileSystem::OnGetFileFromCache(const GetFileFromCacheParams& params, return; } - // If cache file is not found, try to download it from the server instead. + // If cache file is not found, try to download the file from the server + // instead. This logic is rather complicated but here's how this works: + // + // Check if we have enough space, based on the expected file size. + // - if we don't have enough space, try to free up the disk space + // - if we still don't have enough space, return "no space" error + // - if we have enough space, start downloading the file from the server + int64 file_size = 0; + { + base::AutoLock lock(lock_); // To access the root directory. + GDataFileBase* file_base = root_->GetFileByResourceId(resource_id); + if (file_base) + file_size = file_base->file_info().size; + } + + bool* has_enough_space = new bool(false); + PostBlockingPoolSequencedTaskAndReply( + kGDataFileSystemToken, + FROM_HERE, + base::Bind(&GDataFileSystem::FreeDiskSpaceIfNeededFor, + base::Unretained(this), + file_size, + has_enough_space), + base::Bind(&GDataFileSystem::StartDownloadFileIfEnoughSpace, + GetWeakPtrForCurrentThread(), + params, + cache_file_path, + base::Owned(has_enough_space))); +} + +void GDataFileSystem::FreeDiskSpaceIfNeededFor(int64 num_bytes, + bool* has_enough_space) { + // Do nothing and return if we have enough space. + *has_enough_space = HasEnoughSpaceFor(num_bytes); + if (*has_enough_space) + return; + + // Otherwise, try to free up the disk space. + DVLOG(1) << "Freeing up disk space for " << num_bytes; + base::AutoLock lock(lock_); // To access the cache map. + // First remove temporary files from the cache map. + root_->RemoveTemporaryFilesFromCacheMap(); + // Then remove all files under "tmp" directory. + RemoveAllFiles(GetGDataCacheTmpDirectory()); + + // Check the disk space again. + *has_enough_space = HasEnoughSpaceFor(num_bytes); +} + +void GDataFileSystem::FreeDiskSpaceIfNeeded(bool* has_enough_space) { + FreeDiskSpaceIfNeededFor(0, has_enough_space); +} + +void GDataFileSystem::StartDownloadFileIfEnoughSpace( + const GetFileFromCacheParams& params, + const FilePath& cache_file_path, + bool* has_enough_space) { + if (!*has_enough_space) { + // If no enough space, return PLATFORM_FILE_ERROR_NO_SPACE. + if (!params.callback.is_null()) { + params.proxy->PostTask(FROM_HERE, + base::Bind(params.callback, + base::PLATFORM_FILE_ERROR_NO_SPACE, + cache_file_path, + params.mime_type, + REGULAR_FILE)); + } + return; + } + + // We have enough disk space. Start downloading the file. documents_service_->DownloadFile( params.virtual_file_path, params.local_tmp_path, @@ -2304,18 +2429,60 @@ void GDataFileSystem::OnFileDownloaded( GDataErrorCode status, const GURL& content_url, const FilePath& downloaded_file_path) { + // At this point, the disk can be full or nearly full for several reasons: + // - The expected file size was incorrect and the file was larger + // - There was an in-flight download operation and it used up space + // - The disk became full for some user actions we cannot control + // (ex. the user might have downloaded a large file from a regular web site) + // + // If we don't have enough space, we return PLATFORM_FILE_ERROR_NO_SPACE, + // and try to free up space, even if the file was downloaded successfully. + bool* has_enough_space = new bool(false); + PostBlockingPoolSequencedTaskAndReply( + kGDataFileSystemToken, + FROM_HERE, + base::Bind(&GDataFileSystem::FreeDiskSpaceIfNeeded, + base::Unretained(this), + has_enough_space), + base::Bind(&GDataFileSystem::OnFileDownloadedAndSpaceChecked, + GetWeakPtrForCurrentThread(), + params, + status, + content_url, + downloaded_file_path, + base::Owned(has_enough_space))); +} + +void GDataFileSystem::OnFileDownloadedAndSpaceChecked( + const GetFileFromCacheParams& params, + GDataErrorCode status, + const GURL& content_url, + const FilePath& downloaded_file_path, + bool* has_enough_space) { base::PlatformFileError error = GDataToPlatformError(status); // Make sure that downloaded file is properly stored in cache. We don't have // to wait for this operation to finish since the user can already use the // downloaded file. if (error == base::PLATFORM_FILE_OK) { - StoreToCache(params.resource_id, - params.md5, - downloaded_file_path, - FILE_OPERATION_MOVE, - base::Bind(&GDataFileSystem::OnDownloadStoredToCache, - GetWeakPtrForCurrentThread())); + if (*has_enough_space) { + StoreToCache(params.resource_id, + params.md5, + downloaded_file_path, + FILE_OPERATION_MOVE, + base::Bind(&GDataFileSystem::OnDownloadStoredToCache, + GetWeakPtrForCurrentThread())); + } else { + // If we don't have enough space, remove the downloaded file, and + // report "no space" error. + PostBlockingPoolSequencedTask( + kGDataFileSystemToken, + FROM_HERE, + base::Bind(base::IgnoreResult(&file_util::Delete), + downloaded_file_path, + false /* recursive*/)); + error = base::PLATFORM_FILE_ERROR_NO_SPACE; + } } if (!params.callback.is_null()) { @@ -3758,6 +3925,16 @@ void GDataFileSystem::OnFileUnpinned(base::PlatformFileError* error, if (*error == base::PLATFORM_FILE_OK) NotifyFileUnpinned(resource_id, md5); + + // Now the file is moved from "persistent" to "tmp" directory. + // It's a chance to free up space if needed. + bool* has_enough_space = new bool(false); + PostBlockingPoolSequencedTask( + kGDataFileSystemToken, + FROM_HERE, + base::Bind(&GDataFileSystem::FreeDiskSpaceIfNeeded, + base::Unretained(this), + base::Owned(has_enough_space))); } //============= GDataFileSystem: internal helper functions ===================== @@ -3938,4 +4115,9 @@ void GDataFileSystem::InitializePreferenceObserver() { pref_registrar_->Add(prefs::kDisableGDataHostedFiles, this); } +void SetFreeDiskSpaceGetterForTesting(FreeDiskSpaceGetterInterface* getter) { + delete global_free_disk_getter_for_testing; // Safe to delete NULL; + global_free_disk_getter_for_testing = getter; +} + } // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_file_system.h b/chrome/browser/chromeos/gdata/gdata_file_system.h index 5ef450c..27621db 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system.h +++ b/chrome/browser/chromeos/gdata/gdata_file_system.h @@ -793,6 +793,15 @@ class GDataFileSystem : public GDataFileSystemInterface, const GURL& content_url, const FilePath& downloaded_file_path); + // Similar to OnFileDownloaded() but takes |has_enough_space| so we report + // an error in case we don't have enough disk space. + void OnFileDownloadedAndSpaceChecked( + const GetFileFromCacheParams& params, + GDataErrorCode status, + const GURL& content_url, + const FilePath& downloaded_file_path, + bool* has_enough_space); + // Callback for handling internal StoreToCache() calls after downloading // file content. void OnDownloadStoredToCache(base::PlatformFileError error, @@ -1218,6 +1227,22 @@ class GDataFileSystem : public GDataFileSystemInterface, const FilePath& gdata_file_path, const FilePath& cache_file_path); + // Frees up disk space to store the given number of bytes, while keeping + // kMinFreSpace bytes on the disk, if needed. |has_enough_space| is + // updated to indicate if we have enough space. + void FreeDiskSpaceIfNeededFor(int64 num_bytes, + bool* has_enough_space); + + // Frees up disk space if we have less than kMinFreSpace. |has_enough_space| + // is updated to indicate if we have enough space. + void FreeDiskSpaceIfNeeded(bool* has_enough_space); + + // Starts downloading a file if we have enough disk space indicated by + // |has_enough_space|. + void StartDownloadFileIfEnoughSpace(const GetFileFromCacheParams& params, + const FilePath& cache_file_path, + bool* has_enough_space); + // Cache internal helper functions. // Unsafe (unlocked) version of InitializeCacheIfnecessary method. @@ -1318,6 +1343,26 @@ class GDataFileSystem : public GDataFileSystemInterface, ObserverList<Observer> observers_; }; +// The minimum free space to keep. GDataFileSystem::GetFile() returns +// base::PLATFORM_FILE_ERROR_NO_SPACE if the available space is smaller than +// this value. +// +// Copied from cryptohome/homedirs.h. +// TODO(satorux): Share the constant. +const int64 kMinFreeSpace = 512 * 1LL << 20; + +// Interface class used for getting the free disk space. Only for testing. +class FreeDiskSpaceGetterInterface { + public: + virtual ~FreeDiskSpaceGetterInterface() {} + virtual int64 AmountOfFreeDiskSpace() const = 0; +}; + +// Sets the free disk space getter for testing. +// The existing getter is deleted. +void SetFreeDiskSpaceGetterForTesting( + FreeDiskSpaceGetterInterface* getter); + } // namespace gdata #endif // CHROME_BROWSER_CHROMEOS_GDATA_GDATA_FILE_SYSTEM_H_ diff --git a/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc b/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc index 78edaaa..fc7c093 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc +++ b/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc @@ -33,6 +33,7 @@ #include "testing/gtest/include/gtest/gtest.h" using ::testing::AnyNumber; +using ::testing::AtLeast; using ::testing::Eq; using ::testing::IsNull; using ::testing::Ne; @@ -99,6 +100,8 @@ struct InitialCacheResource { "local", gdata::GDataRootDirectory::CACHE_TYPE_PERSISTENT }, }; +const int64 kLotsOfSpace = gdata::kMinFreeSpace * 10; + struct PathToVerify { PathToVerify(const FilePath& in_path_to_scan, const FilePath& in_expected_existing_path) : @@ -114,6 +117,12 @@ struct PathToVerify { namespace gdata { +class MockFreeDiskSpaceGetter : public FreeDiskSpaceGetterInterface { + public: + virtual ~MockFreeDiskSpaceGetter() {} + MOCK_CONST_METHOD0(AmountOfFreeDiskSpace, int64()); +}; + class GDataFileSystemTest : public testing::Test { protected: GDataFileSystemTest() @@ -144,6 +153,10 @@ class GDataFileSystemTest : public testing::Test { EXPECT_CALL(*mock_doc_service_, Initialize(profile_.get())).Times(1); + // Likewise, this will be owned by GDataFileSystem. + mock_free_disk_space_checker_ = new MockFreeDiskSpaceGetter; + SetFreeDiskSpaceGetterForTesting(mock_free_disk_space_checker_); + ASSERT_FALSE(file_system_); file_system_ = new GDataFileSystem(profile_.get(), mock_doc_service_); @@ -158,6 +171,7 @@ class GDataFileSystemTest : public testing::Test { EXPECT_CALL(*mock_doc_service_, CancelAll()).Times(1); delete file_system_; file_system_ = NULL; + SetFreeDiskSpaceGetterForTesting(NULL); // Run the remaining tasks on the main thread, so that reply tasks (2nd // callback of PostTaskAndReply) are run. Otherwise, there will be a leak @@ -259,6 +273,25 @@ class GDataFileSystemTest : public testing::Test { file_origin); } + // Returns true if the cache entry exists for the given resource ID and MD5. + bool CacheEntryExists(const std::string& resource_id, + const std::string& md5) { + GDataRootDirectory::CacheEntry* entry = + file_system_->root_->GetCacheEntry(resource_id, md5); + return entry != NULL; + } + + // Returns true if the cache file exists for the given resource ID and MD5. + bool CacheFileExists(const std::string& resource_id, + const std::string& md5) { + const FilePath file_path = file_system_->GetCacheFilePath( + resource_id, + md5, + GDataRootDirectory::CACHE_TYPE_TMP, + GDataFileSystem::CACHED_FILE_FROM_SERVER); + return file_util::PathExists(file_path); + } + void TestGetCacheFilePath(const std::string& resource_id, const std::string& md5, const std::string& expected_filename) { @@ -875,6 +908,7 @@ class GDataFileSystemTest : public testing::Test { scoped_refptr<CallbackHelper> callback_helper_; GDataFileSystem* file_system_; MockDocumentsService* mock_doc_service_; + MockFreeDiskSpaceGetter* mock_free_disk_space_checker_; scoped_ptr<MockGDataSyncClient> mock_sync_client_; int num_callback_invocations_; @@ -1840,6 +1874,8 @@ TEST_F(GDataFileSystemTest, RemoveFromCacheSimple) { TEST_F(GDataFileSystemTest, PinAndUnpin) { EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .Times(AtLeast(1)).WillRepeatedly(Return(kLotsOfSpace)); std::string resource_id("pdf:1a2b"); std::string md5("abcdef0123456789"); @@ -2077,6 +2113,8 @@ TEST_F(GDataFileSystemTest, DirtyCachePinned) { TEST_F(GDataFileSystemTest, PinAndUnpinDirtyCache) { EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .Times(AtLeast(1)).WillRepeatedly(Return(kLotsOfSpace)); std::string resource_id("pdf:1a2b"); std::string md5("abcdef0123456789"); @@ -2430,7 +2468,7 @@ TEST_F(GDataFileSystemTest, CreateDirectoryWithService) { // EXPECT_EQ(base::PLATFORM_FILE_OK, callback_helper_->last_error_); } -TEST_F(GDataFileSystemTest, GetFile_FromGData) { +TEST_F(GDataFileSystemTest, GetFile_FromGData_EnoughSpace) { EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); LoadRootFeedDocument("root_feed.json"); @@ -2443,6 +2481,11 @@ TEST_F(GDataFileSystemTest, GetFile_FromGData) { GDataFileBase* file_base = FindFile(file_in_root); GDataFile* file = file_base->AsGDataFile(); FilePath downloaded_file = GetCachePathForFile(file); + const int64 file_size = file_base->file_info().size; + + // Pretend we have enough space. + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .Times(2).WillRepeatedly(Return(file_size + kMinFreeSpace)); // The file is obtained with the mock DocumentsService. EXPECT_CALL(*mock_doc_service_, @@ -2453,13 +2496,150 @@ TEST_F(GDataFileSystemTest, GetFile_FromGData) { .Times(1); file_system_->GetFile(file_in_root, callback); - RunAllPendingForIO(); + RunAllPendingForIO(); // Try to get from the cache. + RunAllPendingForIO(); // Check if we have space before downloading. + RunAllPendingForIO(); // Check if we have space after downloading. + EXPECT_EQ(base::PLATFORM_FILE_OK, callback_helper_->last_error_); EXPECT_EQ(REGULAR_FILE, callback_helper_->file_type_); EXPECT_EQ(downloaded_file.value(), callback_helper_->download_path_.value()); } +TEST_F(GDataFileSystemTest, GetFile_FromGData_NoSpaceAtAll) { + EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); + + LoadRootFeedDocument("root_feed.json"); + + GetFileCallback callback = + base::Bind(&CallbackHelper::GetFileCallback, + callback_helper_.get()); + + FilePath file_in_root(FILE_PATH_LITERAL("gdata/File 1.txt")); + GDataFileBase* file_base = FindFile(file_in_root); + GDataFile* file = file_base->AsGDataFile(); + FilePath downloaded_file = GetCachePathForFile(file); + + // Pretend we have no space at all. + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .Times(2).WillRepeatedly(Return(0)); + + // The file is not obtained with the mock DocumentsService, because of no + // space. + EXPECT_CALL(*mock_doc_service_, + DownloadFile(file_in_root, + downloaded_file, + GURL("https://file_content_url/"), + _)) + .Times(0); + + file_system_->GetFile(file_in_root, callback); + RunAllPendingForIO(); // Try to get from the cache. + RunAllPendingForIO(); // Check if we have space before downloading. + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + callback_helper_->last_error_); +} + +TEST_F(GDataFileSystemTest, GetFile_FromGData_NoEnoughSpaceButCanFreeUp) { + EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); + + LoadRootFeedDocument("root_feed.json"); + + GetFileCallback callback = + base::Bind(&CallbackHelper::GetFileCallback, + callback_helper_.get()); + + FilePath file_in_root(FILE_PATH_LITERAL("gdata/File 1.txt")); + GDataFileBase* file_base = FindFile(file_in_root); + GDataFile* file = file_base->AsGDataFile(); + FilePath downloaded_file = GetCachePathForFile(file); + const int64 file_size = file_base->file_info().size; + + // Pretend we have no space first (checked before downloading a file), + // but then start reporting we have space. This is to emulate that + // the disk space was freed up by removing temporary files. + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .WillOnce(Return(0)) + .WillOnce(Return(file_size + kMinFreeSpace)) + .WillOnce(Return(file_size + kMinFreeSpace)); + + // Store something in the temporary cache directory. + TestStoreToCache("<resource_id>", + "<md5>", + GetTestFilePath("root_feed.json"), + base::PLATFORM_FILE_OK, + GDataFile::CACHE_STATE_PRESENT, + GDataRootDirectory::CACHE_TYPE_TMP); + ASSERT_TRUE(CacheEntryExists("<resource_id>", "<md5>")); + ASSERT_TRUE(CacheFileExists("<resource_id>", "<md5>")); + + // The file is obtained with the mock DocumentsService, because of we freed + // up the space. + EXPECT_CALL(*mock_doc_service_, + DownloadFile(file_in_root, + downloaded_file, + GURL("https://file_content_url/"), + _)) + .Times(1); + + file_system_->GetFile(file_in_root, callback); + RunAllPendingForIO(); // Try to get from the cache. + RunAllPendingForIO(); // Check if we have space before downloading. + RunAllPendingForIO(); // Check if we have space after downloading + + EXPECT_EQ(base::PLATFORM_FILE_OK, callback_helper_->last_error_); + EXPECT_EQ(REGULAR_FILE, callback_helper_->file_type_); + EXPECT_EQ(downloaded_file.value(), + callback_helper_->download_path_.value()); + + // The file should be removed in order to free up space, and the cache + // entry should also be removed. + ASSERT_FALSE(CacheEntryExists("<resource_id>", "<md5>")); + ASSERT_FALSE(CacheFileExists("<resource_id>", "<md5>")); +} + +TEST_F(GDataFileSystemTest, GetFile_FromGData_EnoughSpaceButBecomeFull) { + EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); + + LoadRootFeedDocument("root_feed.json"); + + GetFileCallback callback = + base::Bind(&CallbackHelper::GetFileCallback, + callback_helper_.get()); + + FilePath file_in_root(FILE_PATH_LITERAL("gdata/File 1.txt")); + GDataFileBase* file_base = FindFile(file_in_root); + GDataFile* file = file_base->AsGDataFile(); + FilePath downloaded_file = GetCachePathForFile(file); + const int64 file_size = file_base->file_info().size; + + // Pretend we have enough space first (checked before downloading a file), + // but then start reporting we have not enough space. This is to emulate that + // the disk space becomes full after the file is downloaded for some reason + // (ex. the actual file was larger than the expected size). + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .WillOnce(Return(file_size + kMinFreeSpace)) + .WillOnce(Return(kMinFreeSpace - 1)) + .WillOnce(Return(kMinFreeSpace - 1)); + + // The file is obtained with the mock DocumentsService. + EXPECT_CALL(*mock_doc_service_, + DownloadFile(file_in_root, + downloaded_file, + GURL("https://file_content_url/"), + _)) + .Times(1); + + file_system_->GetFile(file_in_root, callback); + RunAllPendingForIO(); // Try to get from the cache. + RunAllPendingForIO(); // Check if we have space before downloading. + RunAllPendingForIO(); // Check if we have space after downloading. + + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, + callback_helper_->last_error_); +} + TEST_F(GDataFileSystemTest, GetFile_FromCache) { EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); @@ -2536,6 +2716,8 @@ TEST_F(GDataFileSystemTest, GetFile_HostedDocument) { TEST_F(GDataFileSystemTest, GetFileForResourceId) { EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); + EXPECT_CALL(*mock_free_disk_space_checker_, AmountOfFreeDiskSpace()) + .Times(AtLeast(1)).WillRepeatedly(Return(kLotsOfSpace)); LoadRootFeedDocument("root_feed.json"); @@ -2559,7 +2741,9 @@ TEST_F(GDataFileSystemTest, GetFileForResourceId) { file_system_->GetFileForResourceId(file->resource_id(), callback); - RunAllPendingForIO(); + RunAllPendingForIO(); // Try to get from the cache. + RunAllPendingForIO(); // Check if we have space before downloading. + RunAllPendingForIO(); // Check if we have space after downloading. EXPECT_EQ(REGULAR_FILE, callback_helper_->file_type_); EXPECT_EQ(downloaded_file.value(), diff --git a/chrome/browser/chromeos/gdata/gdata_files.cc b/chrome/browser/chromeos/gdata/gdata_files.cc index 7bddede..c1cc930 100644 --- a/chrome/browser/chromeos/gdata/gdata_files.cc +++ b/chrome/browser/chromeos/gdata/gdata_files.cc @@ -507,4 +507,18 @@ void GDataRootDirectory::GetCacheState( file_system_->GetCacheState(resource_id, md5, callback); } +void GDataRootDirectory::RemoveTemporaryFilesFromCacheMap() { + CacheMap::iterator iter = cache_map_.begin(); + while (iter != cache_map_.end()) { + CacheEntry* entry = iter->second; + if (entry->sub_dir_type == CACHE_TYPE_TMP) { + delete entry; + // Post-increment the iterator to avoid iterator invalidation. + cache_map_.erase(iter++); + } else { + ++iter; + } + } +} + } // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_files.h b/chrome/browser/chromeos/gdata/gdata_files.h index 105c6e8..5ca3457 100644 --- a/chrome/browser/chromeos/gdata/gdata_files.h +++ b/chrome/browser/chromeos/gdata/gdata_files.h @@ -394,6 +394,9 @@ class GDataRootDirectory : public GDataDirectory { const std::string& md5, const GetCacheStateCallback& callback); + // Remove temporary files (files in CACHE_TYPE_TMP) from the cache map. + void RemoveTemporaryFilesFromCacheMap(); + private: ResourceMap resource_map_; CacheMap cache_map_; diff --git a/chrome/browser/chromeos/gdata/gdata_files_unittest.cc b/chrome/browser/chromeos/gdata/gdata_files_unittest.cc new file mode 100644 index 0000000..d6d3341 --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_files_unittest.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/gdata/gdata_files.h" + +#include <string> +#include <utility> +#include <vector> +#include "testing/gtest/include/gtest/gtest.h" + +namespace gdata { + +TEST(GDataRootDirectoryTest, RemoveTemporaryFilesFromCacheMap) { + scoped_ptr<GDataRootDirectory> root(new GDataRootDirectory(NULL)); + GDataRootDirectory::CacheMap cache_map; + cache_map.insert(std::make_pair( + "<resource_id_1>", + new GDataRootDirectory::CacheEntry( + "<md5>", + GDataRootDirectory::CACHE_TYPE_TMP, + GDataFile::CACHE_STATE_NONE))); + cache_map.insert(std::make_pair( + "<resource_id_2>", + new GDataRootDirectory::CacheEntry( + "<md5>", + GDataRootDirectory::CACHE_TYPE_PINNED, + GDataFile::CACHE_STATE_NONE))); + cache_map.insert(std::make_pair( + "<resource_id_3>", + new GDataRootDirectory::CacheEntry( + "<md5>", + GDataRootDirectory::CACHE_TYPE_OUTGOING, + GDataFile::CACHE_STATE_NONE))); + cache_map.insert(std::make_pair( + "<resource_id_4>", + new GDataRootDirectory::CacheEntry( + "<md5>", + GDataRootDirectory::CACHE_TYPE_TMP, + GDataFile::CACHE_STATE_NONE))); + root->SetCacheMap(cache_map); + root->RemoveTemporaryFilesFromCacheMap(); + // resource 1 and 4 should be gone, as these are CACHE_TYPE_TMP. + ASSERT_TRUE(root->GetCacheEntry("<resource_id_1>", "") == NULL); + ASSERT_TRUE(root->GetCacheEntry("<resource_id_2>", "") != NULL); + ASSERT_TRUE(root->GetCacheEntry("<resource_id_3>", "") != NULL); + ASSERT_TRUE(root->GetCacheEntry("<resource_id_4>", "") == NULL); + +} + +} // namespace gdata diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 9740305..1230540 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1274,6 +1274,7 @@ 'browser/chromeos/extensions/file_browser_notifications_unittest.cc', 'browser/chromeos/external_metrics_unittest.cc', 'browser/chromeos/gdata/gdata_file_system_unittest.cc', + 'browser/chromeos/gdata/gdata_files_unittest.cc', 'browser/chromeos/gdata/gdata_operation_registry_unittest.cc', 'browser/chromeos/gdata/gdata_parser_unittest.cc', 'browser/chromeos/gdata/gdata_sync_client_unittest.cc', |