diff options
Diffstat (limited to 'chrome/browser/chromeos')
4 files changed, 271 insertions, 3 deletions
diff --git a/chrome/browser/chromeos/gdata/gdata_file_system.cc b/chrome/browser/chromeos/gdata/gdata_file_system.cc index 2f7465f..3597cf5 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system.cc +++ b/chrome/browser/chromeos/gdata/gdata_file_system.cc @@ -2177,6 +2177,99 @@ GDataEntry* GDataFileSystem::GetGDataEntryByPath( return entry; } +void GDataFileSystem::UpdateFileByResourceId( + const std::string& resource_id, + const FileOperationCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || + BrowserThread::CurrentlyOn(BrowserThread::IO)); + RunTaskOnUIThread( + base::Bind(&GDataFileSystem::UpdateFileByResourceIdOnUIThread, + ui_weak_ptr_, + resource_id, + CreateRelayCallback(callback))); +} + +void GDataFileSystem::UpdateFileByResourceIdOnUIThread( + const std::string& resource_id, + const FileOperationCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + GDataEntry* entry = root_->GetEntryByResourceId(resource_id); + if (!entry || !entry->AsGDataFile()) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(callback, + base::PLATFORM_FILE_ERROR_NOT_FOUND)); + return; + } + GDataFile* file = entry->AsGDataFile(); + + cache_->GetFileOnUIThread( + resource_id, + file->file_md5(), + base::Bind(&GDataFileSystem::OnGetFileCompleteForUpdateFile, + ui_weak_ptr_, + callback)); +} + +void GDataFileSystem::OnGetFileCompleteForUpdateFile( + const FileOperationCallback& callback, + base::PlatformFileError error, + const std::string& resource_id, + const std::string& md5, + const FilePath& cache_file_path) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (error != base::PLATFORM_FILE_OK) { + if (!callback.is_null()) + callback.Run(error); + return; + } + + GDataEntry* entry = root_->GetEntryByResourceId(resource_id); + if (!entry || !entry->AsGDataFile()) { + if (!callback.is_null()) + callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND); + return; + } + GDataFile* file = entry->AsGDataFile(); + + uploader_->UploadExistingFile( + file->upload_url(), + file->GetFilePath(), + cache_file_path, + file->file_info().size, + file->content_mime_type(), + base::Bind(&GDataFileSystem::OnUpdatedFileUploaded, + ui_weak_ptr_, + callback)); +} + +void GDataFileSystem::OnUpdatedFileUploaded( + const FileOperationCallback& callback, + base::PlatformFileError error, + scoped_ptr<UploadFileInfo> upload_file_info) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(upload_file_info.get()); + + if (error != base::PLATFORM_FILE_OK) { + if (!callback.is_null()) + callback.Run(error); + return; + } + + // See comments in OnTransferCompleted() for why we copy this pointer. + const UploadFileInfo* upload_file_info_ptr = upload_file_info.get(); + AddUploadedFile(upload_file_info_ptr->gdata_path.DirName(), + upload_file_info_ptr->entry.get(), + upload_file_info_ptr->file_path, + GDataCache::FILE_OPERATION_MOVE, + base::Bind(&OnAddUploadFileCompleted, + callback, + error, + base::Passed(&upload_file_info))); +} + void GDataFileSystem::GetCacheState(const std::string& resource_id, const std::string& md5, const GetCacheStateCallback& callback) { @@ -3379,6 +3472,16 @@ void GDataFileSystem::AddUploadedFileOnUIThread( return; } + // Remove an existing entry if present. This is needed when uploading a + // file to update an existing file, rather than adding a new file. + GDataEntry* existing_entry = root_->GetEntryByResourceId( + new_entry->resource_id()); + if (existing_entry && + // This should always match, but just in case. + existing_entry->parent() == parent_dir) { + parent_dir->RemoveEntry(existing_entry); + } + GDataFile* file = new_entry->AsGDataFile(); DCHECK(file); const std::string& resource_id = file->resource_id(); diff --git a/chrome/browser/chromeos/gdata/gdata_file_system.h b/chrome/browser/chromeos/gdata/gdata_file_system.h index f47a98e..b0c39ca 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system.h +++ b/chrome/browser/chromeos/gdata/gdata_file_system.h @@ -284,6 +284,16 @@ class GDataFileSystemInterface { const GetFileCallback& get_file_callback, const GetDownloadDataCallback& get_download_data_callback) = 0; + // Updates a file by the given |resource_id| on the gdata server by + // uploading an updated version. Used for uploading dirty files. The file + // should already be present in the cache. + // + // Can be called from UI/IO thread. |callback| and is run on the calling + // thread. + virtual void UpdateFileByResourceId( + const std::string& resource_id, + const FileOperationCallback& callback) = 0; + // Gets the cache state of file corresponding to |resource_id| and |md5| if it // exists on disk. // Initializes cache if it has not been initialized. @@ -411,6 +421,9 @@ class GDataFileSystem : public GDataFileSystemInterface, const std::string& resource_id, const GetFileCallback& get_file_callback, const GetDownloadDataCallback& get_download_data_callback) OVERRIDE; + virtual void UpdateFileByResourceId( + const std::string& resource_id, + const FileOperationCallback& callback) OVERRIDE; virtual void GetCacheState(const std::string& resource_id, const std::string& md5, const GetCacheStateCallback& callback) OVERRIDE; @@ -1071,6 +1084,22 @@ class GDataFileSystem : public GDataFileSystemInterface, base::PlatformFileError error, const GDataFileProto* file_proto); + // Called when GDataCache::GetFileOnUIThread() is completed for + // UpdateFileByResourceId(). + void OnGetFileCompleteForUpdateFile( + const FileOperationCallback& callback, + base::PlatformFileError error, + const std::string& resource_id, + const std::string& md5, + const FilePath& cache_file_path); + + // Called when GDataUploader::UploadUpdatedFile() is completed for + // UpdateFileByResourceId(). + void OnUpdatedFileUploaded( + const FileOperationCallback& callback, + base::PlatformFileError error, + scoped_ptr<UploadFileInfo> upload_file_info); + // The following functions are used to forward calls to asynchronous public // member functions to UI thread. void SearchAsyncOnUIThread(const std::string& search_query, @@ -1100,6 +1129,9 @@ class GDataFileSystem : public GDataFileSystemInterface, const std::string& resource_id, const GetFileCallback& get_file_callback, const GetDownloadDataCallback& get_download_data_callback); + void UpdateFileByResourceIdOnUIThread( + const std::string& resource_id, + const FileOperationCallback& callback); void GetEntryInfoByPathAsyncOnUIThread( const FilePath& file_path, const GetEntryInfoCallback& callback); diff --git a/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc b/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc index 709a256..9b065af 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc +++ b/chrome/browser/chromeos/gdata/gdata_file_system_unittest.cc @@ -145,12 +145,28 @@ void DriveSearchCallback( message_loop->Quit(); } -// Action used to set mock expectations for GetDocuments. +// Action used to set mock expectations for +// DocumentsService::GetDocumentEntry(). ACTION_P2(MockGetDocumentEntryCallback, status, value) { base::MessageLoopProxy::current()->PostTask(FROM_HERE, base::Bind(arg1, status, base::Passed(value))); } +// Action used to set mock expectations for +// GDataUploaderInterface::UploadExistingFile(). +ACTION_P4(MockUploadExistingFile, + error, gdata_path, local_file_path, document_entry) { + scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo); + upload_file_info->gdata_path = gdata_path; + upload_file_info->file_path = local_file_path; + upload_file_info->entry.reset(document_entry); + base::MessageLoopProxy::current()->PostTask(FROM_HERE, + base::Bind(arg5, error, base::Passed(&upload_file_info))); + + const int kUploadId = 123; + return kUploadId; +} + } // anonymous namespace class MockFreeDiskSpaceGetter : public FreeDiskSpaceGetterInterface { @@ -358,8 +374,7 @@ class GDataFileSystemTest : public testing::Test { } GDataEntry* FindEntryByResourceId(const std::string& resource_id) { - GDataEntry* entry = file_system_->root_->GetEntryByResourceId(resource_id); - return entry ? entry->AsGDataFile() : NULL; + return file_system_->root_->GetEntryByResourceId(resource_id); } // Gets the entry info for |file_path| and compares the contents against @@ -3357,6 +3372,121 @@ TEST_F(GDataFileSystemTest, GetFileByResourceId_FromCache) { callback_helper_->download_path_.value()); } +TEST_F(GDataFileSystemTest, UpdateFileByResourceId_PersistentFile) { + LoadRootFeedDocument("root_feed.json"); + + // This is a file defined in root_feed.json. + const FilePath kFilePath(FILE_PATH_LITERAL("drive/File 1.txt")); + const std::string kResourceId("file:2_file_resource_id"); + const std::string kMd5("3b4382ebefec6e743578c76bbd0575ce"); + + // Pin the file so it'll be store in "persistent" directory. + EXPECT_CALL(*mock_sync_client_, OnCachePinned(kResourceId, kMd5)).Times(1); + TestPin(kResourceId, + kMd5, + base::PLATFORM_FILE_OK, + GDataCache::CACHE_STATE_PINNED, + GDataCache::CACHE_TYPE_PINNED); + + // First store a file to cache. A cache file will be created at: + const FilePath cache_file_path = + GDataCache::GetCacheRootPath(profile_.get()) + .AppendASCII("persistent") + .AppendASCII(kResourceId + "." + kMd5); + TestStoreToCache(kResourceId, + kMd5, + GetTestFilePath("root_feed.json"), // Anything works. + base::PLATFORM_FILE_OK, + GDataCache::CACHE_STATE_PRESENT | + GDataCache::CACHE_STATE_PINNED, + GDataCache::CACHE_TYPE_PERSISTENT); + ASSERT_TRUE(file_util::PathExists(cache_file_path)); + + // Create a DocumentEntry, which is needed to mock + // GDataUploaderInterface::UploadExistingFile(). + // TODO(satorux): This should be cleaned up. crbug.com/134240. + DocumentEntry* document_entry = NULL; + scoped_ptr<base::Value> value(LoadJSONFile("root_feed.json")); + ASSERT_TRUE(value.get()); + base::DictionaryValue* as_dict = NULL; + base::ListValue* entry_list = NULL; + if (value->GetAsDictionary(&as_dict) && + as_dict->GetList("feed.entry", &entry_list)) { + for (size_t i = 0; i < entry_list->GetSize(); ++i) { + base::DictionaryValue* entry = NULL; + std::string resource_id; + if (entry_list->GetDictionary(i, &entry) && + entry->GetString("gd$resourceId.$t", &resource_id) && + resource_id == kResourceId) { + // This will be deleted by UploadExistingFile(). + document_entry = DocumentEntry::CreateFrom(entry); + } + } + } + ASSERT_TRUE(document_entry); + + // The mock uploader will be used to simulate a file upload. + EXPECT_CALL(*mock_uploader_, UploadExistingFile( + GURL("https://file_link_resumable_edit_media/"), + kFilePath, + cache_file_path, + 892721, // The size is written in the root_feed.json. + "audio/mpeg", + _)) // callback + .WillOnce(MockUploadExistingFile( + base::PLATFORM_FILE_OK, + FilePath::FromUTF8Unsafe("drive/File1"), + cache_file_path, + document_entry)); + + // We'll notify the directory change to the observer upon completion. + EXPECT_CALL(*mock_directory_observer_, + OnDirectoryChanged(Eq(FilePath(kGDataRootDirectory)))).Times(1); + + // The callback will be called upon completion of + // UpdateFileByResourceId(). + FileOperationCallback callback = + base::Bind(&CallbackHelper::FileOperationCallback, + callback_helper_.get()); + + // Check the number of files in the root directory. We'll compare the + // number after updating a file. + GDataEntry* root_entry = + FindEntryByResourceId(kGDataRootDirectoryResourceId); + ASSERT_TRUE(root_entry); + GDataDirectory* root_directory = root_entry->AsGDataDirectory(); + ASSERT_TRUE(root_directory); + const size_t num_files_in_root = root_directory->child_files().size(); + + file_system_->UpdateFileByResourceId(kResourceId, callback); + RunAllPendingForIO(); // Get the file from the cache. + RunAllPendingForIO(); // Storing back the file to the cache. + EXPECT_EQ(base::PLATFORM_FILE_OK, callback_helper_->last_error_); + // Make sure that the number of files did not change (i.e. we updated an + // existing file, rather than adding a new file. The number of files + // increases if we don't handle the file update right). + EXPECT_EQ(num_files_in_root, root_directory->child_files().size()); +} + +TEST_F(GDataFileSystemTest, UpdateFileByResourceId_NonexistentFile) { + LoadRootFeedDocument("root_feed.json"); + + // This is nonexistent in root_feed.json. + const FilePath kFilePath(FILE_PATH_LITERAL("drive/Nonexistent.txt")); + const std::string kResourceId("file:nonexistent_resource_id"); + const std::string kMd5("nonexistent_md5"); + + // The callback will be called upon completion of + // UpdateFileByResourceId(). + FileOperationCallback callback = + base::Bind(&CallbackHelper::FileOperationCallback, + callback_helper_.get()); + + file_system_->UpdateFileByResourceId(kResourceId, callback); + RunAllPendingForIO(); // Get the file from the cache. + EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, callback_helper_->last_error_); +} + TEST_F(GDataFileSystemTest, ContentSearch) { LoadRootFeedDocument("root_feed.json"); diff --git a/chrome/browser/chromeos/gdata/mock_gdata_file_system.h b/chrome/browser/chromeos/gdata/mock_gdata_file_system.h index 6954af4..7e36bd4 100644 --- a/chrome/browser/chromeos/gdata/mock_gdata_file_system.h +++ b/chrome/browser/chromeos/gdata/mock_gdata_file_system.h @@ -65,6 +65,9 @@ class MockGDataFileSystem : public GDataFileSystemInterface { void(const std::string& resource_id, const GetFileCallback& get_file_callback, const GetDownloadDataCallback& get_download_data_callback)); + MOCK_METHOD2(UpdateFileByResourceId, + void(const std::string& resource_id, + const FileOperationCallback& callback)); MOCK_METHOD3(GetCacheState, void(const std::string& resource_id, const std::string& md5, const GetCacheStateCallback& callback)); |