diff options
author | satorux@chromium.org <satorux@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-04 09:03:03 +0000 |
---|---|---|
committer | satorux@chromium.org <satorux@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-04 09:03:03 +0000 |
commit | c4aa387495dcdac402d19cf9d02bebb10a2b1029 (patch) | |
tree | 71f3f1b4684d1005fb3b3a3b607405c9783d035d | |
parent | 2c148b7297816a23bace29d840a85c070c273d7b (diff) | |
download | chromium_src-c4aa387495dcdac402d19cf9d02bebb10a2b1029.zip chromium_src-c4aa387495dcdac402d19cf9d02bebb10a2b1029.tar.gz chromium_src-c4aa387495dcdac402d19cf9d02bebb10a2b1029.tar.bz2 |
gdata: Move GDataWapiFeedLoader to a set of new files
This change reduces ~800 lines from gdata_file_system.cc
Along the way move some functions to gdata_util.h, which
are needed for gdata_file_system.cc and gdata_wapi_feed_loader.cc
BUG=138268
TEST=none; just move code around. should not change the behaviors
TBR=ben@chromium.org
for chrome_browser.gypi
Review URL: https://chromiumcodereview.appspot.com/10834170
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150027 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_file_system.cc | 807 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_file_system.h | 160 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_util.cc | 43 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_util.h | 28 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.cc | 717 | ||||
-rw-r--r-- | chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.h | 206 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 2 |
7 files changed, 1015 insertions, 948 deletions
diff --git a/chrome/browser/chromeos/gdata/gdata_file_system.cc b/chrome/browser/chromeos/gdata/gdata_file_system.cc index ccffc8c..7de41a3 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system.cc +++ b/chrome/browser/chromeos/gdata/gdata_file_system.cc @@ -8,11 +8,8 @@ #include <utility> #include "base/bind.h" -#include "base/command_line.h" #include "base/file_util.h" #include "base/json/json_file_value_serializer.h" -#include "base/json/json_reader.h" -#include "base/json/json_writer.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" #include "base/metrics/histogram.h" @@ -28,7 +25,6 @@ #include "chrome/browser/chromeos/gdata/gdata_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/pref_names.h" #include "content/public/browser/browser_thread.h" @@ -43,13 +39,6 @@ namespace { const char kMimeTypeJson[] = "application/json"; const char kMimeTypeOctetStream[] = "application/octet-stream"; -const FilePath::CharType kAccountMetadataFile[] = - FILE_PATH_LITERAL("account_metadata.json"); -const FilePath::CharType kFilesystemProtoFile[] = - FILE_PATH_LITERAL("file_system.pb"); -const FilePath::CharType kResourceMetadataDBFile[] = - FILE_PATH_LITERAL("resource_metadata.db"); - const char kEmptyFilePath[] = "/dev/null"; // GData update check interval (in seconds). @@ -59,67 +48,6 @@ const int kGDataUpdateCheckIntervalInSec = 5; const int kGDataUpdateCheckIntervalInSec = 60; #endif -// Update the fetch progress UI per every this number of feeds. -const int kFetchUiUpdateStep = 10; - -// Schedule for dumping root file system proto buffers to disk depending its -// total protobuffer size in MB. -typedef struct { - double size; - int timeout; -} SerializationTimetable; - -SerializationTimetable kSerializeTimetable[] = { -#ifndef NDEBUG - {0.5, 0}, // Less than 0.5MB, dump immediately. - {-1, 1}, // Any size, dump if older than 1 minute. -#else - {0.5, 0}, // Less than 0.5MB, dump immediately. - {1.0, 15}, // Less than 1.0MB, dump after 15 minutes. - {2.0, 30}, - {4.0, 60}, - {-1, 120}, // Any size, dump if older than 120 minutes. -#endif -}; - -// Returns true if file system is due to be serialized on disk based on it -// |serialized_size| and |last_serialized| timestamp. -bool ShouldSerializeFileSystemNow(size_t serialized_size, - const base::Time& last_serialized) { - const double size_in_mb = serialized_size / 1048576.0; - const int last_proto_dump_in_min = - (base::Time::Now() - last_serialized).InMinutes(); - for (size_t i = 0; i < arraysize(kSerializeTimetable); i++) { - if ((size_in_mb < kSerializeTimetable[i].size || - kSerializeTimetable[i].size == -1) && - last_proto_dump_in_min >= kSerializeTimetable[i].timeout) { - return true; - } - } - return false; -} - -// Converts gdata error code into file platform error code. -GDataFileError GDataToGDataFileError(GDataErrorCode status) { - switch (status) { - case HTTP_SUCCESS: - case HTTP_CREATED: - return GDATA_FILE_OK; - case HTTP_UNAUTHORIZED: - case HTTP_FORBIDDEN: - return GDATA_FILE_ERROR_ACCESS_DENIED; - case HTTP_NOT_FOUND: - return GDATA_FILE_ERROR_NOT_FOUND; - case GDATA_PARSE_ERROR: - case GDATA_FILE_ERROR: - return GDATA_FILE_ERROR_ABORT; - case GDATA_NO_CONNECTION: - return GDATA_FILE_ERROR_NO_CONNECTION; - default: - return GDATA_FILE_ERROR_FAILED; - } -} - //================================ Helper functions ============================ // Invoked upon completion of TransferRegularFile initiated by Copy. @@ -200,66 +128,6 @@ class InitialLoadObserver : public GDataFileSystemInterface::Observer { base::Closure callback_; }; -// Saves the string |serialized_proto| to a file at |path| on a blocking thread. -void SaveProtoOnBlockingPool(const FilePath& path, - scoped_ptr<std::string> serialized_proto) { - const int file_size = static_cast<int>(serialized_proto->length()); - if (file_util::WriteFile(path, serialized_proto->data(), file_size) != - file_size) { - LOG(WARNING) << "GData proto file can't be stored at " - << path.value(); - if (!file_util::Delete(path, true)) { - LOG(WARNING) << "GData proto file can't be deleted at " - << path.value(); - } - } -} - -// Loads the file at |path| into the string |serialized_proto| on a blocking -// thread. -void LoadProtoOnBlockingPool(const FilePath& path, - LoadRootFeedParams* params) { - base::PlatformFileInfo info; - if (!file_util::GetFileInfo(path, &info)) { - params->load_error = GDATA_FILE_ERROR_NOT_FOUND; - return; - } - params->last_modified = info.last_modified; - if (!file_util::ReadFileToString(path, ¶ms->proto)) { - LOG(WARNING) << "Proto file not found at " << path.value(); - params->load_error = GDATA_FILE_ERROR_NOT_FOUND; - return; - } - params->load_error = GDATA_FILE_OK; -} - -// Saves json file content content in |feed| to |file_pathname| on blocking -// pool. Used for debugging. -void SaveFeedOnBlockingPoolForDebugging( - const FilePath& file_path, - scoped_ptr<base::Value> feed) { - std::string json; - base::JSONWriter::WriteWithOptions(feed.get(), - base::JSONWriter::OPTIONS_PRETTY_PRINT, - &json); - - int file_size = static_cast<int>(json.length()); - if (file_util::WriteFile(file_path, json.data(), file_size) != file_size) { - LOG(WARNING) << "GData metadata file can't be stored at " - << file_path.value(); - if (!file_util::Delete(file_path, true)) { - LOG(WARNING) << "GData metadata file can't be deleted at " - << file_path.value(); - return; - } - } -} - -bool UseLevelDB() { - return CommandLine::ForCurrentProcess()->HasSwitch( - switches::kUseLevelDBForGData); -} - // Gets the file size of |local_file|. void GetLocalFileSizeOnBlockingPool(const FilePath& local_file, GDataFileError* error, @@ -510,32 +378,6 @@ CallbackType CreateRelayCallback(const CallbackType& callback) { callback); } -// Wrapper around BrowserThread::PostTask to post a task to the blocking -// pool with the given sequence token. -void PostBlockingPoolSequencedTask( - const tracked_objects::Location& from_here, - base::SequencedTaskRunner* blocking_task_runner, - const base::Closure& task) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - const bool posted = blocking_task_runner->PostTask(from_here, task); - DCHECK(posted); -} - -// Similar to PostBlockingPoolSequencedTask() but this one takes a reply -// callback that runs on the calling thread. -void PostBlockingPoolSequencedTaskAndReply( - const tracked_objects::Location& from_here, - base::SequencedTaskRunner* blocking_task_runner, - const base::Closure& request_task, - const base::Closure& reply_task) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - const bool posted = blocking_task_runner->PostTaskAndReply( - from_here, request_task, reply_task); - DCHECK(posted); -} - // Helper function for binding |path| to GetEntryInfoWithFilePathCallback and // create GetEntryInfoCallback. void RunGetEntryInfoWithFilePathCallback( @@ -549,119 +391,6 @@ void RunGetEntryInfoWithFilePathCallback( } // namespace -GDataWapiFeedLoader::GDataWapiFeedLoader( - GDataDirectoryService* directory_service, - DocumentsServiceInterface* documents_service, - DriveWebAppsRegistryInterface* webapps_registry, - GDataCache* cache, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) - : directory_service_(directory_service), - documents_service_(documents_service), - webapps_registry_(webapps_registry), - cache_(cache), - blocking_task_runner_(blocking_task_runner), - weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { -} - -GDataWapiFeedLoader::~GDataWapiFeedLoader() { -} - -void GDataWapiFeedLoader::AddObserver(Observer* observer) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - observers_.AddObserver(observer); -} - -void GDataWapiFeedLoader::RemoveObserver(Observer* observer) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - observers_.RemoveObserver(observer); -} - -// Defines set of parameters sent to callback OnNotifyDocumentFeedFetched(). -// This is a trick to update the number of fetched documents frequently on -// UI. Due to performance reason, we need to fetch a number of files at -// a time. However, it'll take long time, and a user has no way to know -// the current update state. In order to make users confortable, -// we increment the number of fetched documents with more frequent but smaller -// steps than actual fetching. -struct GetDocumentsUiState { - explicit GetDocumentsUiState(base::TimeTicks start_time) - : num_fetched_documents(0), - num_showing_documents(0), - start_time(start_time), - weak_ptr_factory(this) { - } - - // The number of fetched documents. - int num_fetched_documents; - - // The number documents shown on UI. - int num_showing_documents; - - // When the UI update has started. - base::TimeTicks start_time; - - // Time elapsed since the feed fetching was started. - base::TimeDelta feed_fetching_elapsed_time; - - base::WeakPtrFactory<GetDocumentsUiState> weak_ptr_factory; -}; - -// Defines set of parameters sent to callback OnGetDocuments(). -// TODO(satorux): Move this to a new file: crbug.com/138268 -struct GetDocumentsParams { - GetDocumentsParams(int start_changestamp, - int root_feed_changestamp, - std::vector<DocumentFeed*>* feed_list, - bool should_fetch_multiple_feeds, - const FilePath& search_file_path, - const std::string& search_query, - const std::string& directory_resource_id, - const FindEntryCallback& callback, - GetDocumentsUiState* ui_state); - ~GetDocumentsParams(); - - // Changestamps are positive numbers in increasing order. The difference - // between two changestamps is proportional equal to number of items in - // delta feed between them - bigger the difference, more likely bigger - // number of items in delta feeds. - int start_changestamp; - int root_feed_changestamp; - scoped_ptr<std::vector<DocumentFeed*> > feed_list; - // Should we stop after getting first feed chunk, even if there is more - // data. - bool should_fetch_multiple_feeds; - FilePath search_file_path; - std::string search_query; - std::string directory_resource_id; - FindEntryCallback callback; - scoped_ptr<GetDocumentsUiState> ui_state; -}; - -GetDocumentsParams::GetDocumentsParams( - int start_changestamp, - int root_feed_changestamp, - std::vector<DocumentFeed*>* feed_list, - bool should_fetch_multiple_feeds, - const FilePath& search_file_path, - const std::string& search_query, - const std::string& directory_resource_id, - const FindEntryCallback& callback, - GetDocumentsUiState* ui_state) - : start_changestamp(start_changestamp), - root_feed_changestamp(root_feed_changestamp), - feed_list(feed_list), - should_fetch_multiple_feeds(should_fetch_multiple_feeds), - search_file_path(search_file_path), - search_query(search_query), - directory_resource_id(directory_resource_id), - callback(callback), - ui_state(ui_state) { -} - -GetDocumentsParams::~GetDocumentsParams() { - STLDeleteElements(feed_list.get()); -} - // GDataFileSystem::CreateDirectoryParams struct implementation. struct GDataFileSystem::CreateDirectoryParams { CreateDirectoryParams(const FilePath& created_directory_path, @@ -975,195 +704,6 @@ void GDataFileSystem::FindEntryByPathSyncOnUIThread( directory_service_->FindEntryByPathAndRunSync(search_file_path, callback); } -void GDataWapiFeedLoader::ReloadFromServerIfNeeded( - ContentOrigin initial_origin, - int local_changestamp, - const FilePath& search_file_path, - const FindEntryCallback& callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - DVLOG(1) << "ReloadFeedFromServerIfNeeded local_changestamp=" - << local_changestamp << ", initial_origin=" << initial_origin; - - // First fetch the latest changestamp to see if there were any new changes - // there at all. - documents_service_->GetAccountMetadata( - base::Bind(&GDataWapiFeedLoader::OnGetAccountMetadata, - weak_ptr_factory_.GetWeakPtr(), - initial_origin, - local_changestamp, - search_file_path, - callback)); -} - -void GDataWapiFeedLoader::OnGetAccountMetadata( - ContentOrigin initial_origin, - int local_changestamp, - const FilePath& search_file_path, - const FindEntryCallback& callback, - GDataErrorCode status, - scoped_ptr<base::Value> feed_data) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - GDataFileError error = GDataToGDataFileError(status); - if (error != GDATA_FILE_OK) { - // Get changes starting from the next changestamp from what we have locally. - LoadFromServer(initial_origin, - local_changestamp + 1, 0, - true, /* should_fetch_multiple_feeds */ - search_file_path, - std::string() /* no search query */, - GURL(), /* feed not explicitly set */ - std::string() /* no directory resource ID */, - callback, - base::Bind(&GDataWapiFeedLoader::OnFeedFromServerLoaded, - weak_ptr_factory_.GetWeakPtr())); - return; - } - - scoped_ptr<AccountMetadataFeed> account_metadata; - if (feed_data.get()) { - account_metadata = AccountMetadataFeed::CreateFrom(*feed_data); -#ifndef NDEBUG - // Save account metadata feed for analysis. - const FilePath path = - cache_->GetCacheDirectoryPath(GDataCache::CACHE_TYPE_META).Append( - kAccountMetadataFile); - PostBlockingPoolSequencedTask( - FROM_HERE, - blocking_task_runner_, - base::Bind(&SaveFeedOnBlockingPoolForDebugging, - path, base::Passed(&feed_data))); -#endif - } - - if (!account_metadata.get()) { - LoadFromServer(initial_origin, - local_changestamp + 1, 0, - true, /* should_fetch_multiple_feeds */ - search_file_path, - std::string() /* no search query */, - GURL(), /* feed not explicitly set */ - std::string() /* no directory resource ID */, - callback, - base::Bind(&GDataWapiFeedLoader::OnFeedFromServerLoaded, - weak_ptr_factory_.GetWeakPtr())); - return; - } - - webapps_registry_->UpdateFromFeed(account_metadata.get()); - - bool changes_detected = true; - if (local_changestamp >= account_metadata->largest_changestamp()) { - if (local_changestamp > account_metadata->largest_changestamp()) { - LOG(WARNING) << "Cached client feed is fresher than server, client = " - << local_changestamp - << ", server = " - << account_metadata->largest_changestamp(); - } - // If our cache holds the latest state from the server, change the - // state to FROM_SERVER. - directory_service_->set_origin( - initial_origin == FROM_CACHE ? FROM_SERVER : initial_origin); - changes_detected = false; - } - - // No changes detected, continue with search as planned. - if (!changes_detected) { - if (!callback.is_null()) { - directory_service_->FindEntryByPathAndRunSync(search_file_path, - callback); - } - return; - } - - // Load changes from the server. - LoadFromServer(initial_origin, - local_changestamp > 0 ? local_changestamp + 1 : 0, - account_metadata->largest_changestamp(), - true, /* should_fetch_multiple_feeds */ - search_file_path, - std::string() /* no search query */, - GURL(), /* feed not explicitly set */ - std::string() /* no directory resource ID */, - callback, - base::Bind(&GDataWapiFeedLoader::OnFeedFromServerLoaded, - weak_ptr_factory_.GetWeakPtr())); -} - -void GDataWapiFeedLoader::LoadFromServer( - ContentOrigin initial_origin, - int start_changestamp, - int root_feed_changestamp, - bool should_fetch_multiple_feeds, - const FilePath& search_file_path, - const std::string& search_query, - const GURL& feed_to_load, - const std::string& directory_resource_id, - const FindEntryCallback& entry_found_callback, - const LoadDocumentFeedCallback& feed_load_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - // |feed_list| will contain the list of all collected feed updates that - // we will receive through calls of DocumentsService::GetDocuments(). - scoped_ptr<std::vector<DocumentFeed*> > feed_list( - new std::vector<DocumentFeed*>); - const base::TimeTicks start_time = base::TimeTicks::Now(); - documents_service_->GetDocuments( - feed_to_load, - start_changestamp, - search_query, - directory_resource_id, - base::Bind(&GDataWapiFeedLoader::OnGetDocuments, - weak_ptr_factory_.GetWeakPtr(), - initial_origin, - feed_load_callback, - base::Owned(new GetDocumentsParams(start_changestamp, - root_feed_changestamp, - feed_list.release(), - should_fetch_multiple_feeds, - search_file_path, - search_query, - directory_resource_id, - entry_found_callback, - NULL)), - start_time)); -} - -void GDataWapiFeedLoader::OnFeedFromServerLoaded(GetDocumentsParams* params, - GDataFileError error) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (error != GDATA_FILE_OK) { - if (!params->callback.is_null()) - params->callback.Run(error, NULL); - return; - } - - error = UpdateFromFeed(*params->feed_list, - params->start_changestamp, - params->root_feed_changestamp); - - if (error != GDATA_FILE_OK) { - if (!params->callback.is_null()) - params->callback.Run(error, NULL); - - return; - } - - // Save file system metadata to disk. - SaveFileSystem(); - - // If we had someone to report this too, then this retrieval was done in a - // context of search... so continue search. - if (!params->callback.is_null()) { - directory_service_->FindEntryByPathAndRunSync(params->search_file_path, - params->callback); - } - - FOR_EACH_OBSERVER(Observer, observers_, OnFeedFromServerLoaded()); -} - void GDataFileSystem::TransferFileFromRemoteToLocal( const FilePath& remote_src_file_path, const FilePath& local_dest_file_path, @@ -1222,7 +762,7 @@ void GDataFileSystem::TransferFileFromLocalToRemoteAfterGetEntryInfo( } std::string* resource_id = new std::string; - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GetDocumentResourceIdOnBlockingPool, @@ -1271,7 +811,7 @@ void GDataFileSystem::TransferRegularFile( new GDataFileError(GDATA_FILE_OK); int64* file_size = new int64; std::string* content_type = new std::string; - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GetLocalFileInfoOnBlockingPool, @@ -1499,7 +1039,7 @@ void GDataFileSystem::OnGetFileCompleteForTransferFile( // CopyLocalFileOnBlockingPool. GDataFileError* copy_file_error = new GDataFileError(GDATA_FILE_OK); - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&CopyLocalFileOnBlockingPool, @@ -2022,7 +1562,7 @@ void GDataFileSystem::GetResolvedFileByPath( FilePath* temp_file_path = new FilePath; std::string* mime_type = new std::string; GDataFileType* file_type = new GDataFileType(REGULAR_FILE); - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&CreateDocumentJsonFileOnBlockingPool, @@ -2174,7 +1714,7 @@ void GDataFileSystem::OnGetDocumentEntry(const FilePath& cache_file_path, scoped_ptr<base::Value> data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); scoped_ptr<GDataEntry> fresh_entry; if (error == GDATA_FILE_OK) { @@ -2209,7 +1749,7 @@ void GDataFileSystem::OnGetDocumentEntry(const FilePath& cache_file_path, directory_service_->RefreshFile(fresh_entry_as_file.Pass()); bool* has_enough_space = new bool(false); - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GDataCache::FreeDiskSpaceIfNeededFor, @@ -2542,7 +2082,7 @@ void GDataFileSystem::OnGetFileCompleteForUpdateFile( // file size information stored in GDataEntry is not correct. GDataFileError* get_size_error = new GDataFileError(GDATA_FILE_ERROR_FAILED); int64* file_size = new int64(-1); - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GetLocalFileSizeOnBlockingPool, @@ -2655,7 +2195,7 @@ void GDataFileSystem::OnGetAvailableSpace( scoped_ptr<base::Value> data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error != GDATA_FILE_OK) { callback.Run(error, -1, -1); return; @@ -2680,7 +2220,7 @@ void GDataFileSystem::OnCreateDirectoryCompleted( scoped_ptr<base::Value> data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error != GDATA_FILE_OK) { if (!params.callback.is_null()) params.callback.Run(error); @@ -2822,191 +2362,6 @@ void GDataFileSystem::SearchAsyncOnUIThread( base::Bind(&GDataFileSystem::OnSearch, ui_weak_ptr_, callback)); } -void GDataWapiFeedLoader::OnGetDocuments( - ContentOrigin initial_origin, - const LoadDocumentFeedCallback& callback, - GetDocumentsParams* params, - base::TimeTicks start_time, - GDataErrorCode status, - scoped_ptr<base::Value> data) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (params->feed_list->empty()) { - UMA_HISTOGRAM_TIMES("Gdata.InitialFeedLoadTime", - base::TimeTicks::Now() - start_time); - } - - GDataFileError error = GDataToGDataFileError(status); - if (error == GDATA_FILE_OK && - (!data.get() || data->GetType() != Value::TYPE_DICTIONARY)) { - error = GDATA_FILE_ERROR_FAILED; - } - - if (error != GDATA_FILE_OK) { - directory_service_->set_origin(initial_origin); - - if (!callback.is_null()) - callback.Run(params, error); - - return; - } - - GURL next_feed_url; - scoped_ptr<DocumentFeed> current_feed(DocumentFeed::ExtractAndParse(*data)); - if (!current_feed.get()) { - if (!callback.is_null()) { - callback.Run(params, GDATA_FILE_ERROR_FAILED); - } - - return; - } - const bool has_next_feed_url = current_feed->GetNextFeedURL(&next_feed_url); - -#ifndef NDEBUG - // Save initial root feed for analysis. - std::string file_name = - base::StringPrintf("DEBUG_feed_%d.json", - params->start_changestamp); - PostBlockingPoolSequencedTask( - FROM_HERE, - blocking_task_runner_, - base::Bind(&SaveFeedOnBlockingPoolForDebugging, - cache_->GetCacheDirectoryPath( - GDataCache::CACHE_TYPE_META).Append(file_name), - base::Passed(&data))); -#endif - - // Add the current feed to the list of collected feeds for this directory. - params->feed_list->push_back(current_feed.release()); - - // Compute and notify the number of entries fetched so far. - int num_accumulated_entries = 0; - for (size_t i = 0; i < params->feed_list->size(); ++i) - num_accumulated_entries += params->feed_list->at(i)->entries().size(); - - // Check if we need to collect more data to complete the directory list. - if (params->should_fetch_multiple_feeds && has_next_feed_url && - !next_feed_url.is_empty()) { - // Post an UI update event to make the UI smoother. - GetDocumentsUiState* ui_state = params->ui_state.get(); - if (ui_state == NULL) { - ui_state = new GetDocumentsUiState(base::TimeTicks::Now()); - params->ui_state.reset(ui_state); - } - DCHECK(ui_state); - - if ((ui_state->num_fetched_documents - ui_state->num_showing_documents) - < kFetchUiUpdateStep) { - // Currently the UI update is stopped. Start UI periodic callback. - MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&GDataWapiFeedLoader::OnNotifyDocumentFeedFetched, - weak_ptr_factory_.GetWeakPtr(), - ui_state->weak_ptr_factory.GetWeakPtr())); - } - ui_state->num_fetched_documents = num_accumulated_entries; - ui_state->feed_fetching_elapsed_time = base::TimeTicks::Now() - start_time; - - // Kick of the remaining part of the feeds. - documents_service_->GetDocuments( - next_feed_url, - params->start_changestamp, - params->search_query, - params->directory_resource_id, - base::Bind(&GDataWapiFeedLoader::OnGetDocuments, - weak_ptr_factory_.GetWeakPtr(), - initial_origin, - callback, - base::Owned( - new GetDocumentsParams( - params->start_changestamp, - params->root_feed_changestamp, - params->feed_list.release(), - params->should_fetch_multiple_feeds, - params->search_file_path, - params->search_query, - params->directory_resource_id, - params->callback, - params->ui_state.release())), - start_time)); - return; - } - - // Notify the observers that a document feed is fetched. - FOR_EACH_OBSERVER(Observer, observers_, - OnDocumentFeedFetched(num_accumulated_entries)); - - UMA_HISTOGRAM_TIMES("Gdata.EntireFeedLoadTime", - base::TimeTicks::Now() - start_time); - - if (!callback.is_null()) - callback.Run(params, error); -} - -void GDataWapiFeedLoader::OnNotifyDocumentFeedFetched( - base::WeakPtr<GetDocumentsUiState> ui_state) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (!ui_state) { - // The ui state instance is already released, which means the fetching - // is done and we don't need to update any more. - return; - } - - base::TimeDelta elapsed_time = - base::TimeTicks::Now() - ui_state->start_time; - - if (ui_state->num_showing_documents + kFetchUiUpdateStep <= - ui_state->num_fetched_documents) { - ui_state->num_showing_documents += kFetchUiUpdateStep; - FOR_EACH_OBSERVER(Observer, observers_, - OnDocumentFeedFetched(ui_state->num_showing_documents)); - - int num_remaining_ui_updates = - (ui_state->num_fetched_documents - ui_state->num_showing_documents) - / kFetchUiUpdateStep; - if (num_remaining_ui_updates > 0) { - // Heuristically, we use fetched time duration to calculate the next - // UI update timing. - base::TimeDelta remaining_duration = - ui_state->feed_fetching_elapsed_time - elapsed_time; - MessageLoop::current()->PostDelayedTask( - FROM_HERE, - base::Bind(&GDataWapiFeedLoader::OnNotifyDocumentFeedFetched, - weak_ptr_factory_.GetWeakPtr(), - ui_state->weak_ptr_factory.GetWeakPtr()), - remaining_duration / num_remaining_ui_updates); - } - } -} - -void GDataWapiFeedLoader::LoadFromCache( - bool should_load_from_server, - const FilePath& search_file_path, - const FindEntryCallback& callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - LoadRootFeedParams* params = new LoadRootFeedParams(search_file_path, - should_load_from_server, - callback); - FilePath path = cache_->GetCacheDirectoryPath(GDataCache::CACHE_TYPE_META); - if (UseLevelDB()) { - path = path.Append(kResourceMetadataDBFile); - directory_service_->InitFromDB(path, blocking_task_runner_, - base::Bind( - &GDataWapiFeedLoader::ContinueWithInitializedDirectoryService, - weak_ptr_factory_.GetWeakPtr(), - base::Owned(params))); - } else { - path = path.Append(kFilesystemProtoFile); - BrowserThread::GetBlockingPool()->PostTaskAndReply(FROM_HERE, - base::Bind(&LoadProtoOnBlockingPool, path, params), - base::Bind(&GDataWapiFeedLoader::OnProtoLoaded, - weak_ptr_factory_.GetWeakPtr(), - base::Owned(params))); - } -} - void GDataFileSystem::OnDirectoryChanged(const FilePath& directory_path) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -3049,99 +2404,6 @@ GDataFileError GDataFileSystem::UpdateFromFeedForTesting( root_feed_changestamp); } -void GDataWapiFeedLoader::OnProtoLoaded(LoadRootFeedParams* params) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - // If we have already received updates from the server, bail out. - if (directory_service_->origin() == FROM_SERVER) - return; - - // Update directory structure only if everything is OK and we haven't yet - // received the feed from the server yet. - if (params->load_error == GDATA_FILE_OK) { - DVLOG(1) << "ParseFromString"; - if (directory_service_->ParseFromString(params->proto)) { - directory_service_->set_last_serialized(params->last_modified); - directory_service_->set_serialized_size(params->proto.size()); - } else { - params->load_error = GDATA_FILE_ERROR_FAILED; - LOG(WARNING) << "Parse of cached proto file failed"; - } - } - - ContinueWithInitializedDirectoryService(params, params->load_error); -} - -void GDataWapiFeedLoader::ContinueWithInitializedDirectoryService( - LoadRootFeedParams* params, - GDataFileError error) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - DVLOG(1) << "Time elapsed to load directory service from disk=" - << (base::Time::Now() - params->load_start_time).InMilliseconds() - << " milliseconds"; - - FindEntryCallback callback = params->callback; - // If we got feed content from cache, try search over it. - if (error == GDATA_FILE_OK && !callback.is_null()) { - // Continue file content search operation if the delegate hasn't terminated - // this search branch already. - directory_service_->FindEntryByPathAndRunSync(params->search_file_path, - callback); - callback.Reset(); - } - - if (!params->should_load_from_server) - return; - - // Decide the |initial_origin| to pass to ReloadFromServerIfNeeded(). - // This is used to restore directory content origin to its initial value when - // we fail to retrieve the feed from server. - // By default, if directory content is not yet initialized, restore content - // origin to UNINITIALIZED in case of failure. - ContentOrigin initial_origin = UNINITIALIZED; - if (directory_service_->origin() != INITIALIZING) { - // If directory content is already initialized, restore content origin - // to FROM_CACHE in case of failure. - initial_origin = FROM_CACHE; - directory_service_->set_origin(REFRESHING); - } - - // Kick of the retrieval of the feed from server. If we have previously - // |reported| to the original callback, then we just need to refresh the - // content without continuing search upon operation completion. - ReloadFromServerIfNeeded(initial_origin, - directory_service_->largest_changestamp(), - params->search_file_path, - callback); -} - -void GDataWapiFeedLoader::SaveFileSystem() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (!ShouldSerializeFileSystemNow(directory_service_->serialized_size(), - directory_service_->last_serialized())) { - return; - } - - if (UseLevelDB()) { - directory_service_->SaveToDB(); - } else { - const FilePath path = - cache_->GetCacheDirectoryPath(GDataCache::CACHE_TYPE_META).Append( - kFilesystemProtoFile); - scoped_ptr<std::string> serialized_proto(new std::string()); - directory_service_->SerializeToString(serialized_proto.get()); - directory_service_->set_last_serialized(base::Time::Now()); - directory_service_->set_serialized_size(serialized_proto->size()); - PostBlockingPoolSequencedTask( - FROM_HERE, - blocking_task_runner_, - base::Bind(&SaveProtoOnBlockingPool, path, - base::Passed(serialized_proto.Pass()))); - } -} - void GDataFileSystem::OnFilePathUpdated(const FileOperationCallback& callback, GDataFileError error, const FilePath& file_path) { @@ -3159,7 +2421,7 @@ void GDataFileSystem::OnRenameResourceCompleted( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); FilePath updated_file_path; - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error == GDATA_FILE_OK) error = RenameFileOnFilesystem(file_path, new_name, &updated_file_path); @@ -3174,7 +2436,7 @@ void GDataFileSystem::OnCopyDocumentCompleted( scoped_ptr<base::Value> data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error != GDATA_FILE_OK) { if (!callback.is_null()) callback.Run(error); @@ -3214,7 +2476,7 @@ void GDataFileSystem::OnAddEntryToDirectoryCompleted( const GURL& document_url) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error == GDATA_FILE_OK) { GDataEntry* entry = directory_service_->FindEntryByPathSync(file_path); if (entry) { @@ -3238,7 +2500,7 @@ void GDataFileSystem::OnRemoveEntryFromDirectoryCompleted( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); FilePath updated_file_path = file_path; - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error == GDATA_FILE_OK) error = RemoveEntryFromDirectoryOnFilesystem(file_path, dir_path, &updated_file_path); @@ -3254,7 +2516,7 @@ void GDataFileSystem::OnRemovedDocument( const GURL& document_url) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(status); if (error == GDATA_FILE_OK) error = RemoveEntryFromFileSystem(file_path); @@ -3292,7 +2554,7 @@ void GDataFileSystem::OnFileDownloaded( // 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( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GDataCache::FreeDiskSpaceIfNeededFor, @@ -3328,7 +2590,7 @@ void GDataFileSystem::OnFileDownloadedAndSpaceChecked( bool* has_enough_space) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - GDataFileError error = GDataToGDataFileError(status); + GDataFileError error = util::GDataToGDataFileError(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 @@ -3345,7 +2607,7 @@ void GDataFileSystem::OnFileDownloadedAndSpaceChecked( } else { // If we don't have enough space, remove the downloaded file, and // report "no space" error. - PostBlockingPoolSequencedTask( + util::PostBlockingPoolSequencedTask( FROM_HERE, blocking_task_runner_, base::Bind(base::IgnoreResult(&file_util::Delete), @@ -3466,37 +2728,6 @@ GDataFileError GDataFileSystem::RemoveEntryFromFileSystem( return GDATA_FILE_OK; } -GDataFileError GDataWapiFeedLoader::UpdateFromFeed( - const std::vector<DocumentFeed*>& feed_list, - int start_changestamp, - int root_feed_changestamp) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DVLOG(1) << "Updating directory with a feed"; - - std::set<FilePath> changed_dirs; - - GDataWapiFeedProcessor feed_processor(directory_service_); - const GDataFileError error = feed_processor.ApplyFeeds( - feed_list, - start_changestamp, - root_feed_changestamp, - &changed_dirs); - - // Don't send directory content change notification while performing - // the initial content retrieval. - const bool should_notify_directory_changed = (start_changestamp != 0); - if (should_notify_directory_changed) { - for (std::set<FilePath>::iterator dir_iter = changed_dirs.begin(); - dir_iter != changed_dirs.end(); ++dir_iter) { - FOR_EACH_OBSERVER(Observer, observers_, - OnDirectoryChanged(*dir_iter)); - } - } - - return error; -} - - // static void GDataFileSystem::RemoveStaleEntryOnUpload(const std::string& resource_id, GDataDirectory* parent_dir, @@ -3984,7 +3215,7 @@ void GDataFileSystem::OnGetCacheFilePathCompleteForCloseFile( // the cache file. base::PlatformFileInfo* file_info = new base::PlatformFileInfo; bool* get_file_info_result = new bool(false); - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GetFileInfoOnBlockingPool, @@ -4165,7 +3396,7 @@ void GDataFileSystem::CheckLocalModificationAndRunAfterGetCacheFile( // If the cache is dirty, obtain the file info from the cache file itself. base::PlatformFileInfo* file_info = new base::PlatformFileInfo; bool* get_file_info_result = new bool(false); - PostBlockingPoolSequencedTaskAndReply( + util::PostBlockingPoolSequencedTaskAndReply( FROM_HERE, blocking_task_runner_, base::Bind(&GetFileInfoOnBlockingPool, diff --git a/chrome/browser/chromeos/gdata/gdata_file_system.h b/chrome/browser/chromeos/gdata/gdata_file_system.h index 707ddc3..bc3d247 100644 --- a/chrome/browser/chromeos/gdata/gdata_file_system.h +++ b/chrome/browser/chromeos/gdata/gdata_file_system.h @@ -19,6 +19,7 @@ #include "chrome/browser/chromeos/gdata/gdata_file_system_interface.h" #include "chrome/browser/chromeos/gdata/gdata_errorcode.h" #include "chrome/browser/chromeos/gdata/gdata_files.h" +#include "chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.h" #include "chrome/browser/chromeos/gdata/gdata_wapi_feed_processor.h" #include "chrome/browser/prefs/pref_change_registrar.h" #include "content/public/browser/notification_observer.h" @@ -34,167 +35,8 @@ namespace gdata { class DocumentsServiceInterface; class DriveWebAppsRegistryInterface; class GDataWapiFeedLoader; -struct GetDocumentsParams; -struct GetDocumentsUiState; struct UploadFileInfo; -// Callback run as a response to LoadFromServer. -// -// TODO(satorux): Move this to a new file: crbug.com/138268 -typedef base::Callback<void(GetDocumentsParams* params, - GDataFileError error)> - LoadDocumentFeedCallback; - -// GDataWapiFeedLoader is used to load feeds from WAPI (codename for -// Documents List API) and load the cached proto file. -// -// TODO(satorux): Move this to a new file: crbug.com/138268 -class GDataWapiFeedLoader { - public: - // Used to notify events from the loader. - // All events are notified on UI thread. - class Observer { - public: - // Triggered when a content of a directory has been changed. - // |directory_path| is a virtual directory path representing the - // changed directory. - virtual void OnDirectoryChanged(const FilePath& directory_path) {} - - // Triggered when a document feed is fetched. |num_accumulated_entries| - // tells the number of entries fetched so far. - virtual void OnDocumentFeedFetched(int num_accumulated_entries) {} - - // Triggered when the feed from the server is loaded. - virtual void OnFeedFromServerLoaded() {} - - protected: - virtual ~Observer() {} - }; - - GDataWapiFeedLoader( - GDataDirectoryService* directory_service, - DocumentsServiceInterface* documents_service, - DriveWebAppsRegistryInterface* webapps_registry, - GDataCache* cache, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_); - ~GDataWapiFeedLoader(); - - // Adds and removes the observer. - void AddObserver(Observer* observer); - void RemoveObserver(Observer* observer); - - // Starts root feed load from the cache. If successful, it will try to find - // the file upon retrieval completion. In addition to that, it will - // initiate retrieval of the root feed from the server unless - // |should_load_from_server| is set to false. |should_load_from_server| is - // false only for testing. - void LoadFromCache(bool should_load_from_server, - const FilePath& search_file_path, - const FindEntryCallback& callback); - - // Starts root feed load from the server. Value of |start_changestamp| - // determines the type of feed to load - 0 means root feed, every other - // value would trigger delta feed. - // In the case of loading the root feed we use |root_feed_changestamp| as its - // initial changestamp value since it does not come with that info. - // When done |load_feed_callback| is invoked. - // |entry_found_callback| is used only when this is invoked while searching - // for file info, and is used in |load_feed_callback|. If successful, it will - // try to find the file upon retrieval completion. - // |should_fetch_multiple_feeds| is true iff don't want to stop feed loading - // after we retrieve first feed chunk. - // If invoked as a part of content search, query will be set in - // |search_query|. - // If |feed_to_load| is set, this is feed url that will be used to load feed. - void LoadFromServer( - ContentOrigin initial_origin, - int start_changestamp, - int root_feed_changestamp, - bool should_fetch_multiple_feeds, - const FilePath& search_file_path, - const std::string& search_query, - const GURL& feed_to_load, - const std::string& directory_resource_id, - const FindEntryCallback& entry_found_callback, - const LoadDocumentFeedCallback& feed_load_callback); - - // Retrieves account metadata and determines from the last change timestamp - // if the feed content loading from the server needs to be initiated. - void ReloadFromServerIfNeeded( - ContentOrigin initial_origin, - int local_changestamp, - const FilePath& search_file_path, - const FindEntryCallback& callback); - - // Updates whole directory structure feeds collected in |feed_list|. - // On success, returns PLATFORM_FILE_OK. Record file statistics as UMA - // histograms. - // - // See comments at GDataWapiFeedProcessor::ApplyFeeds() for - // |start_changestamp| and |root_feed_changestamp|. - GDataFileError UpdateFromFeed( - const std::vector<DocumentFeed*>& feed_list, - int start_changestamp, - int root_feed_changestamp); - - private: - // Callback for handling root directory refresh from the cache. - void OnProtoLoaded(LoadRootFeedParams* params); - - // Continues handling root directory refresh after the directory service - // is fully loaded. - void ContinueWithInitializedDirectoryService(LoadRootFeedParams* params, - GDataFileError error); - - // Helper callback for handling results of metadata retrieval initiated from - // ReloadFeedFromServerIfNeeded(). This method makes a decision about fetching - // the content of the root feed during the root directory refresh process. - void OnGetAccountMetadata( - ContentOrigin initial_origin, - int local_changestamp, - const FilePath& search_file_path, - const FindEntryCallback& callback, - GDataErrorCode status, - scoped_ptr<base::Value> feed_data); - - // Callback for handling feed content fetching while searching for file info. - // This callback is invoked after async feed fetch operation that was - // invoked by StartDirectoryRefresh() completes. This callback will update - // the content of the refreshed directory object and continue initially - // started FindEntryByPath() request. - void OnFeedFromServerLoaded(GetDocumentsParams* params, - GDataFileError error); - - // Callback for handling response from |GDataDocumentsService::GetDocuments|. - // Invokes |callback| when done. - void OnGetDocuments( - ContentOrigin initial_origin, - const LoadDocumentFeedCallback& callback, - GetDocumentsParams* params, - base::TimeTicks start_time, - GDataErrorCode status, - scoped_ptr<base::Value> data); - - // Save filesystem to disk. - void SaveFileSystem(); - - // Callback for handling UI updates caused by document fetching. - void OnNotifyDocumentFeedFetched( - base::WeakPtr<GetDocumentsUiState> ui_state); - - GDataDirectoryService* directory_service_; // Not owned. - DocumentsServiceInterface* documents_service_; // Not owned. - DriveWebAppsRegistryInterface* webapps_registry_; // Not owned. - GDataCache* cache_; // Not owned. - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; - ObserverList<Observer> observers_; - - // Note: This should remain the last member so it'll be destroyed and - // invalidate its weak pointers before any other members are destroyed. - base::WeakPtrFactory<GDataWapiFeedLoader> weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(GDataWapiFeedLoader); -}; - // The production implementation of GDataFileSystemInterface. class GDataFileSystem : public GDataFileSystemInterface, public GDataWapiFeedLoader::Observer, diff --git a/chrome/browser/chromeos/gdata/gdata_util.cc b/chrome/browser/chromeos/gdata/gdata_util.cc index 3db1670..92a3c78 100644 --- a/chrome/browser/chromeos/gdata/gdata_util.cc +++ b/chrome/browser/chromeos/gdata/gdata_util.cc @@ -21,6 +21,7 @@ #include "base/stringprintf.h" #include "base/threading/sequenced_worker_pool.h" #include "base/time.h" +#include "base/tracked_objects.h" #include "chrome/browser/chromeos/gdata/file_write_helper.h" #include "chrome/browser/chromeos/gdata/gdata.pb.h" #include "chrome/browser/chromeos/gdata/gdata_file_system_interface.h" @@ -604,5 +605,47 @@ void PrepareWritableFileAndRun(Profile* profile, } } +GDataFileError GDataToGDataFileError(GDataErrorCode status) { + switch (status) { + case HTTP_SUCCESS: + case HTTP_CREATED: + return GDATA_FILE_OK; + case HTTP_UNAUTHORIZED: + case HTTP_FORBIDDEN: + return GDATA_FILE_ERROR_ACCESS_DENIED; + case HTTP_NOT_FOUND: + return GDATA_FILE_ERROR_NOT_FOUND; + case GDATA_PARSE_ERROR: + case GDATA_FILE_ERROR: + return GDATA_FILE_ERROR_ABORT; + case GDATA_NO_CONNECTION: + return GDATA_FILE_ERROR_NO_CONNECTION; + default: + return GDATA_FILE_ERROR_FAILED; + } +} + +void PostBlockingPoolSequencedTask( + const tracked_objects::Location& from_here, + base::SequencedTaskRunner* blocking_task_runner, + const base::Closure& task) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + const bool posted = blocking_task_runner->PostTask(from_here, task); + DCHECK(posted); +} + +void PostBlockingPoolSequencedTaskAndReply( + const tracked_objects::Location& from_here, + base::SequencedTaskRunner* blocking_task_runner, + const base::Closure& request_task, + const base::Closure& reply_task) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + const bool posted = blocking_task_runner->PostTaskAndReply( + from_here, request_task, reply_task); + DCHECK(posted); +} + } // namespace util } // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_util.h b/chrome/browser/chromeos/gdata/gdata_util.h index 04e5e6d..d7640db 100644 --- a/chrome/browser/chromeos/gdata/gdata_util.h +++ b/chrome/browser/chromeos/gdata/gdata_util.h @@ -18,6 +18,14 @@ class FilePath; class Profile; +namespace base { +class SequencedTaskRunner; +} // namespace base + +namespace tracked_objects { +class Location; +} // tracked_objects + namespace gdata { namespace util { @@ -112,7 +120,7 @@ std::string FormatTimeAsString(const base::Time& time); // Callback type for PrepareWritableFilePathAndRun. typedef base::Callback<void (GDataFileError, const FilePath& path)> - OpenFileCallback; + OpenFileCallback; // Invokes |callback| on blocking thread pool, after converting virtual |path| // string like "/special/drive/foo.txt" to the concrete local cache file path. @@ -126,6 +134,24 @@ void PrepareWritableFileAndRun(Profile* profile, const FilePath& path, const OpenFileCallback& callback); +// Converts gdata error code into file platform error code. +GDataFileError GDataToGDataFileError(GDataErrorCode status); + +// Wrapper around BrowserThread::PostTask to post a task to the blocking +// pool with the given sequence token. +void PostBlockingPoolSequencedTask( + const tracked_objects::Location& from_here, + base::SequencedTaskRunner* blocking_task_runner, + const base::Closure& task); + +// Similar to PostBlockingPoolSequencedTask() but this one takes a reply +// callback that runs on the calling thread. +void PostBlockingPoolSequencedTaskAndReply( + const tracked_objects::Location& from_here, + base::SequencedTaskRunner* blocking_task_runner, + const base::Closure& request_task, + const base::Closure& reply_task); + } // namespace util } // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.cc b/chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.cc new file mode 100644 index 0000000..b5eb7cd --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.cc @@ -0,0 +1,717 @@ +// 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_wapi_feed_loader.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/stringprintf.h" +#include "base/threading/sequenced_worker_pool.h" +#include "chrome/browser/chromeos/gdata/drive_webapps_registry.h" +#include "chrome/browser/chromeos/gdata/gdata_cache.h" +#include "chrome/browser/chromeos/gdata/gdata_documents_service.h" +#include "chrome/browser/chromeos/gdata/gdata_util.h" +#include "chrome/browser/chromeos/gdata/gdata_wapi_feed_processor.h" +#include "chrome/common/chrome_switches.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +namespace gdata { + +namespace { + +const FilePath::CharType kAccountMetadataFile[] = + FILE_PATH_LITERAL("account_metadata.json"); +const FilePath::CharType kFilesystemProtoFile[] = + FILE_PATH_LITERAL("file_system.pb"); +const FilePath::CharType kResourceMetadataDBFile[] = + FILE_PATH_LITERAL("resource_metadata.db"); + +// Update the fetch progress UI per every this number of feeds. +const int kFetchUiUpdateStep = 10; + +// Schedule for dumping root file system proto buffers to disk depending its +// total protobuffer size in MB. +typedef struct { + double size; + int timeout; +} SerializationTimetable; + +SerializationTimetable kSerializeTimetable[] = { +#ifndef NDEBUG + {0.5, 0}, // Less than 0.5MB, dump immediately. + {-1, 1}, // Any size, dump if older than 1 minute. +#else + {0.5, 0}, // Less than 0.5MB, dump immediately. + {1.0, 15}, // Less than 1.0MB, dump after 15 minutes. + {2.0, 30}, + {4.0, 60}, + {-1, 120}, // Any size, dump if older than 120 minutes. +#endif +}; + +// Loads the file at |path| into the string |serialized_proto| on a blocking +// thread. +void LoadProtoOnBlockingPool(const FilePath& path, + LoadRootFeedParams* params) { + base::PlatformFileInfo info; + if (!file_util::GetFileInfo(path, &info)) { + params->load_error = GDATA_FILE_ERROR_NOT_FOUND; + return; + } + params->last_modified = info.last_modified; + if (!file_util::ReadFileToString(path, ¶ms->proto)) { + LOG(WARNING) << "Proto file not found at " << path.value(); + params->load_error = GDATA_FILE_ERROR_NOT_FOUND; + return; + } + params->load_error = GDATA_FILE_OK; +} + +// Saves json file content content in |feed| to |file_pathname| on blocking +// pool. Used for debugging. +void SaveFeedOnBlockingPoolForDebugging( + const FilePath& file_path, + scoped_ptr<base::Value> feed) { + std::string json; + base::JSONWriter::WriteWithOptions(feed.get(), + base::JSONWriter::OPTIONS_PRETTY_PRINT, + &json); + + int file_size = static_cast<int>(json.length()); + if (file_util::WriteFile(file_path, json.data(), file_size) != file_size) { + LOG(WARNING) << "GData metadata file can't be stored at " + << file_path.value(); + if (!file_util::Delete(file_path, true)) { + LOG(WARNING) << "GData metadata file can't be deleted at " + << file_path.value(); + return; + } + } +} + +// Returns true if file system is due to be serialized on disk based on it +// |serialized_size| and |last_serialized| timestamp. +bool ShouldSerializeFileSystemNow(size_t serialized_size, + const base::Time& last_serialized) { + const double size_in_mb = serialized_size / 1048576.0; + const int last_proto_dump_in_min = + (base::Time::Now() - last_serialized).InMinutes(); + for (size_t i = 0; i < arraysize(kSerializeTimetable); i++) { + if ((size_in_mb < kSerializeTimetable[i].size || + kSerializeTimetable[i].size == -1) && + last_proto_dump_in_min >= kSerializeTimetable[i].timeout) { + return true; + } + } + return false; +} + +// Saves the string |serialized_proto| to a file at |path| on a blocking thread. +void SaveProtoOnBlockingPool(const FilePath& path, + scoped_ptr<std::string> serialized_proto) { + const int file_size = static_cast<int>(serialized_proto->length()); + if (file_util::WriteFile(path, serialized_proto->data(), file_size) != + file_size) { + LOG(WARNING) << "GData proto file can't be stored at " + << path.value(); + if (!file_util::Delete(path, true)) { + LOG(WARNING) << "GData proto file can't be deleted at " + << path.value(); + } + } +} + +bool UseLevelDB() { + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kUseLevelDBForGData); +} + +} // namespace + +GetDocumentsParams::GetDocumentsParams( + int start_changestamp, + int root_feed_changestamp, + std::vector<DocumentFeed*>* feed_list, + bool should_fetch_multiple_feeds, + const FilePath& search_file_path, + const std::string& search_query, + const std::string& directory_resource_id, + const FindEntryCallback& callback, + GetDocumentsUiState* ui_state) + : start_changestamp(start_changestamp), + root_feed_changestamp(root_feed_changestamp), + feed_list(feed_list), + should_fetch_multiple_feeds(should_fetch_multiple_feeds), + search_file_path(search_file_path), + search_query(search_query), + directory_resource_id(directory_resource_id), + callback(callback), + ui_state(ui_state) { +} + +GetDocumentsParams::~GetDocumentsParams() { + STLDeleteElements(feed_list.get()); +} + +// Defines set of parameters sent to callback OnNotifyDocumentFeedFetched(). +// This is a trick to update the number of fetched documents frequently on +// UI. Due to performance reason, we need to fetch a number of files at +// a time. However, it'll take long time, and a user has no way to know +// the current update state. In order to make users confortable, +// we increment the number of fetched documents with more frequent but smaller +// steps than actual fetching. +struct GetDocumentsUiState { + explicit GetDocumentsUiState(base::TimeTicks start_time) + : num_fetched_documents(0), + num_showing_documents(0), + start_time(start_time), + weak_ptr_factory(this) { + } + + // The number of fetched documents. + int num_fetched_documents; + + // The number documents shown on UI. + int num_showing_documents; + + // When the UI update has started. + base::TimeTicks start_time; + + // Time elapsed since the feed fetching was started. + base::TimeDelta feed_fetching_elapsed_time; + + base::WeakPtrFactory<GetDocumentsUiState> weak_ptr_factory; +}; + +GDataWapiFeedLoader::GDataWapiFeedLoader( + GDataDirectoryService* directory_service, + DocumentsServiceInterface* documents_service, + DriveWebAppsRegistryInterface* webapps_registry, + GDataCache* cache, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) + : directory_service_(directory_service), + documents_service_(documents_service), + webapps_registry_(webapps_registry), + cache_(cache), + blocking_task_runner_(blocking_task_runner), + weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { +} + +GDataWapiFeedLoader::~GDataWapiFeedLoader() { +} + +void GDataWapiFeedLoader::AddObserver(Observer* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + observers_.AddObserver(observer); +} + +void GDataWapiFeedLoader::RemoveObserver(Observer* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + observers_.RemoveObserver(observer); +} + +void GDataWapiFeedLoader::ReloadFromServerIfNeeded( + ContentOrigin initial_origin, + int local_changestamp, + const FilePath& search_file_path, + const FindEntryCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + DVLOG(1) << "ReloadFeedFromServerIfNeeded local_changestamp=" + << local_changestamp << ", initial_origin=" << initial_origin; + + // First fetch the latest changestamp to see if there were any new changes + // there at all. + documents_service_->GetAccountMetadata( + base::Bind(&GDataWapiFeedLoader::OnGetAccountMetadata, + weak_ptr_factory_.GetWeakPtr(), + initial_origin, + local_changestamp, + search_file_path, + callback)); +} + +void GDataWapiFeedLoader::OnGetAccountMetadata( + ContentOrigin initial_origin, + int local_changestamp, + const FilePath& search_file_path, + const FindEntryCallback& callback, + GDataErrorCode status, + scoped_ptr<base::Value> feed_data) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + GDataFileError error = util::GDataToGDataFileError(status); + if (error != GDATA_FILE_OK) { + // Get changes starting from the next changestamp from what we have locally. + LoadFromServer(initial_origin, + local_changestamp + 1, 0, + true, /* should_fetch_multiple_feeds */ + search_file_path, + std::string() /* no search query */, + GURL(), /* feed not explicitly set */ + std::string() /* no directory resource ID */, + callback, + base::Bind(&GDataWapiFeedLoader::OnFeedFromServerLoaded, + weak_ptr_factory_.GetWeakPtr())); + return; + } + + scoped_ptr<AccountMetadataFeed> account_metadata; + if (feed_data.get()) { + account_metadata = AccountMetadataFeed::CreateFrom(*feed_data); +#ifndef NDEBUG + // Save account metadata feed for analysis. + const FilePath path = + cache_->GetCacheDirectoryPath(GDataCache::CACHE_TYPE_META).Append( + kAccountMetadataFile); + util::PostBlockingPoolSequencedTask( + FROM_HERE, + blocking_task_runner_, + base::Bind(&SaveFeedOnBlockingPoolForDebugging, + path, base::Passed(&feed_data))); +#endif + } + + if (!account_metadata.get()) { + LoadFromServer(initial_origin, + local_changestamp + 1, 0, + true, /* should_fetch_multiple_feeds */ + search_file_path, + std::string() /* no search query */, + GURL(), /* feed not explicitly set */ + std::string() /* no directory resource ID */, + callback, + base::Bind(&GDataWapiFeedLoader::OnFeedFromServerLoaded, + weak_ptr_factory_.GetWeakPtr())); + return; + } + + webapps_registry_->UpdateFromFeed(account_metadata.get()); + + bool changes_detected = true; + if (local_changestamp >= account_metadata->largest_changestamp()) { + if (local_changestamp > account_metadata->largest_changestamp()) { + LOG(WARNING) << "Cached client feed is fresher than server, client = " + << local_changestamp + << ", server = " + << account_metadata->largest_changestamp(); + } + // If our cache holds the latest state from the server, change the + // state to FROM_SERVER. + directory_service_->set_origin( + initial_origin == FROM_CACHE ? FROM_SERVER : initial_origin); + changes_detected = false; + } + + // No changes detected, continue with search as planned. + if (!changes_detected) { + if (!callback.is_null()) { + directory_service_->FindEntryByPathAndRunSync(search_file_path, + callback); + } + return; + } + + // Load changes from the server. + LoadFromServer(initial_origin, + local_changestamp > 0 ? local_changestamp + 1 : 0, + account_metadata->largest_changestamp(), + true, /* should_fetch_multiple_feeds */ + search_file_path, + std::string() /* no search query */, + GURL(), /* feed not explicitly set */ + std::string() /* no directory resource ID */, + callback, + base::Bind(&GDataWapiFeedLoader::OnFeedFromServerLoaded, + weak_ptr_factory_.GetWeakPtr())); +} + +void GDataWapiFeedLoader::LoadFromServer( + ContentOrigin initial_origin, + int start_changestamp, + int root_feed_changestamp, + bool should_fetch_multiple_feeds, + const FilePath& search_file_path, + const std::string& search_query, + const GURL& feed_to_load, + const std::string& directory_resource_id, + const FindEntryCallback& entry_found_callback, + const LoadDocumentFeedCallback& feed_load_callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // |feed_list| will contain the list of all collected feed updates that + // we will receive through calls of DocumentsService::GetDocuments(). + scoped_ptr<std::vector<DocumentFeed*> > feed_list( + new std::vector<DocumentFeed*>); + const base::TimeTicks start_time = base::TimeTicks::Now(); + documents_service_->GetDocuments( + feed_to_load, + start_changestamp, + search_query, + directory_resource_id, + base::Bind(&GDataWapiFeedLoader::OnGetDocuments, + weak_ptr_factory_.GetWeakPtr(), + initial_origin, + feed_load_callback, + base::Owned(new GetDocumentsParams(start_changestamp, + root_feed_changestamp, + feed_list.release(), + should_fetch_multiple_feeds, + search_file_path, + search_query, + directory_resource_id, + entry_found_callback, + NULL)), + start_time)); +} + +void GDataWapiFeedLoader::OnFeedFromServerLoaded(GetDocumentsParams* params, + GDataFileError error) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (error != GDATA_FILE_OK) { + if (!params->callback.is_null()) + params->callback.Run(error, NULL); + return; + } + + error = UpdateFromFeed(*params->feed_list, + params->start_changestamp, + params->root_feed_changestamp); + + if (error != GDATA_FILE_OK) { + if (!params->callback.is_null()) + params->callback.Run(error, NULL); + + return; + } + + // Save file system metadata to disk. + SaveFileSystem(); + + // If we had someone to report this too, then this retrieval was done in a + // context of search... so continue search. + if (!params->callback.is_null()) { + directory_service_->FindEntryByPathAndRunSync(params->search_file_path, + params->callback); + } + + FOR_EACH_OBSERVER(Observer, observers_, OnFeedFromServerLoaded()); +} + +void GDataWapiFeedLoader::OnGetDocuments( + ContentOrigin initial_origin, + const LoadDocumentFeedCallback& callback, + GetDocumentsParams* params, + base::TimeTicks start_time, + GDataErrorCode status, + scoped_ptr<base::Value> data) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (params->feed_list->empty()) { + UMA_HISTOGRAM_TIMES("Gdata.InitialFeedLoadTime", + base::TimeTicks::Now() - start_time); + } + + GDataFileError error = util::GDataToGDataFileError(status); + if (error == GDATA_FILE_OK && + (!data.get() || data->GetType() != Value::TYPE_DICTIONARY)) { + error = GDATA_FILE_ERROR_FAILED; + } + + if (error != GDATA_FILE_OK) { + directory_service_->set_origin(initial_origin); + + if (!callback.is_null()) + callback.Run(params, error); + + return; + } + + GURL next_feed_url; + scoped_ptr<DocumentFeed> current_feed(DocumentFeed::ExtractAndParse(*data)); + if (!current_feed.get()) { + if (!callback.is_null()) { + callback.Run(params, GDATA_FILE_ERROR_FAILED); + } + + return; + } + const bool has_next_feed_url = current_feed->GetNextFeedURL(&next_feed_url); + +#ifndef NDEBUG + // Save initial root feed for analysis. + std::string file_name = + base::StringPrintf("DEBUG_feed_%d.json", + params->start_changestamp); + util::PostBlockingPoolSequencedTask( + FROM_HERE, + blocking_task_runner_, + base::Bind(&SaveFeedOnBlockingPoolForDebugging, + cache_->GetCacheDirectoryPath( + GDataCache::CACHE_TYPE_META).Append(file_name), + base::Passed(&data))); +#endif + + // Add the current feed to the list of collected feeds for this directory. + params->feed_list->push_back(current_feed.release()); + + // Compute and notify the number of entries fetched so far. + int num_accumulated_entries = 0; + for (size_t i = 0; i < params->feed_list->size(); ++i) + num_accumulated_entries += params->feed_list->at(i)->entries().size(); + + // Check if we need to collect more data to complete the directory list. + if (params->should_fetch_multiple_feeds && has_next_feed_url && + !next_feed_url.is_empty()) { + // Post an UI update event to make the UI smoother. + GetDocumentsUiState* ui_state = params->ui_state.get(); + if (ui_state == NULL) { + ui_state = new GetDocumentsUiState(base::TimeTicks::Now()); + params->ui_state.reset(ui_state); + } + DCHECK(ui_state); + + if ((ui_state->num_fetched_documents - ui_state->num_showing_documents) + < kFetchUiUpdateStep) { + // Currently the UI update is stopped. Start UI periodic callback. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&GDataWapiFeedLoader::OnNotifyDocumentFeedFetched, + weak_ptr_factory_.GetWeakPtr(), + ui_state->weak_ptr_factory.GetWeakPtr())); + } + ui_state->num_fetched_documents = num_accumulated_entries; + ui_state->feed_fetching_elapsed_time = base::TimeTicks::Now() - start_time; + + // Kick of the remaining part of the feeds. + documents_service_->GetDocuments( + next_feed_url, + params->start_changestamp, + params->search_query, + params->directory_resource_id, + base::Bind(&GDataWapiFeedLoader::OnGetDocuments, + weak_ptr_factory_.GetWeakPtr(), + initial_origin, + callback, + base::Owned( + new GetDocumentsParams( + params->start_changestamp, + params->root_feed_changestamp, + params->feed_list.release(), + params->should_fetch_multiple_feeds, + params->search_file_path, + params->search_query, + params->directory_resource_id, + params->callback, + params->ui_state.release())), + start_time)); + return; + } + + // Notify the observers that a document feed is fetched. + FOR_EACH_OBSERVER(Observer, observers_, + OnDocumentFeedFetched(num_accumulated_entries)); + + UMA_HISTOGRAM_TIMES("Gdata.EntireFeedLoadTime", + base::TimeTicks::Now() - start_time); + + if (!callback.is_null()) + callback.Run(params, error); +} + +void GDataWapiFeedLoader::OnNotifyDocumentFeedFetched( + base::WeakPtr<GetDocumentsUiState> ui_state) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (!ui_state) { + // The ui state instance is already released, which means the fetching + // is done and we don't need to update any more. + return; + } + + base::TimeDelta elapsed_time = + base::TimeTicks::Now() - ui_state->start_time; + + if (ui_state->num_showing_documents + kFetchUiUpdateStep <= + ui_state->num_fetched_documents) { + ui_state->num_showing_documents += kFetchUiUpdateStep; + FOR_EACH_OBSERVER(Observer, observers_, + OnDocumentFeedFetched(ui_state->num_showing_documents)); + + int num_remaining_ui_updates = + (ui_state->num_fetched_documents - ui_state->num_showing_documents) + / kFetchUiUpdateStep; + if (num_remaining_ui_updates > 0) { + // Heuristically, we use fetched time duration to calculate the next + // UI update timing. + base::TimeDelta remaining_duration = + ui_state->feed_fetching_elapsed_time - elapsed_time; + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&GDataWapiFeedLoader::OnNotifyDocumentFeedFetched, + weak_ptr_factory_.GetWeakPtr(), + ui_state->weak_ptr_factory.GetWeakPtr()), + remaining_duration / num_remaining_ui_updates); + } + } +} + +void GDataWapiFeedLoader::LoadFromCache( + bool should_load_from_server, + const FilePath& search_file_path, + const FindEntryCallback& callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + LoadRootFeedParams* params = new LoadRootFeedParams(search_file_path, + should_load_from_server, + callback); + FilePath path = cache_->GetCacheDirectoryPath(GDataCache::CACHE_TYPE_META); + if (UseLevelDB()) { + path = path.Append(kResourceMetadataDBFile); + directory_service_->InitFromDB(path, blocking_task_runner_, + base::Bind( + &GDataWapiFeedLoader::ContinueWithInitializedDirectoryService, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(params))); + } else { + path = path.Append(kFilesystemProtoFile); + BrowserThread::GetBlockingPool()->PostTaskAndReply(FROM_HERE, + base::Bind(&LoadProtoOnBlockingPool, path, params), + base::Bind(&GDataWapiFeedLoader::OnProtoLoaded, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(params))); + } +} + +void GDataWapiFeedLoader::OnProtoLoaded(LoadRootFeedParams* params) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // If we have already received updates from the server, bail out. + if (directory_service_->origin() == FROM_SERVER) + return; + + // Update directory structure only if everything is OK and we haven't yet + // received the feed from the server yet. + if (params->load_error == GDATA_FILE_OK) { + DVLOG(1) << "ParseFromString"; + if (directory_service_->ParseFromString(params->proto)) { + directory_service_->set_last_serialized(params->last_modified); + directory_service_->set_serialized_size(params->proto.size()); + } else { + params->load_error = GDATA_FILE_ERROR_FAILED; + LOG(WARNING) << "Parse of cached proto file failed"; + } + } + + ContinueWithInitializedDirectoryService(params, params->load_error); +} + +void GDataWapiFeedLoader::ContinueWithInitializedDirectoryService( + LoadRootFeedParams* params, + GDataFileError error) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + DVLOG(1) << "Time elapsed to load directory service from disk=" + << (base::Time::Now() - params->load_start_time).InMilliseconds() + << " milliseconds"; + + FindEntryCallback callback = params->callback; + // If we got feed content from cache, try search over it. + if (error == GDATA_FILE_OK && !callback.is_null()) { + // Continue file content search operation if the delegate hasn't terminated + // this search branch already. + directory_service_->FindEntryByPathAndRunSync(params->search_file_path, + callback); + callback.Reset(); + } + + if (!params->should_load_from_server) + return; + + // Decide the |initial_origin| to pass to ReloadFromServerIfNeeded(). + // This is used to restore directory content origin to its initial value when + // we fail to retrieve the feed from server. + // By default, if directory content is not yet initialized, restore content + // origin to UNINITIALIZED in case of failure. + ContentOrigin initial_origin = UNINITIALIZED; + if (directory_service_->origin() != INITIALIZING) { + // If directory content is already initialized, restore content origin + // to FROM_CACHE in case of failure. + initial_origin = FROM_CACHE; + directory_service_->set_origin(REFRESHING); + } + + // Kick of the retrieval of the feed from server. If we have previously + // |reported| to the original callback, then we just need to refresh the + // content without continuing search upon operation completion. + ReloadFromServerIfNeeded(initial_origin, + directory_service_->largest_changestamp(), + params->search_file_path, + callback); +} + +void GDataWapiFeedLoader::SaveFileSystem() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (!ShouldSerializeFileSystemNow(directory_service_->serialized_size(), + directory_service_->last_serialized())) { + return; + } + + if (UseLevelDB()) { + directory_service_->SaveToDB(); + } else { + const FilePath path = + cache_->GetCacheDirectoryPath(GDataCache::CACHE_TYPE_META).Append( + kFilesystemProtoFile); + scoped_ptr<std::string> serialized_proto(new std::string()); + directory_service_->SerializeToString(serialized_proto.get()); + directory_service_->set_last_serialized(base::Time::Now()); + directory_service_->set_serialized_size(serialized_proto->size()); + util::PostBlockingPoolSequencedTask( + FROM_HERE, + blocking_task_runner_, + base::Bind(&SaveProtoOnBlockingPool, path, + base::Passed(serialized_proto.Pass()))); + } +} + +GDataFileError GDataWapiFeedLoader::UpdateFromFeed( + const std::vector<DocumentFeed*>& feed_list, + int start_changestamp, + int root_feed_changestamp) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DVLOG(1) << "Updating directory with a feed"; + + std::set<FilePath> changed_dirs; + + GDataWapiFeedProcessor feed_processor(directory_service_); + const GDataFileError error = feed_processor.ApplyFeeds( + feed_list, + start_changestamp, + root_feed_changestamp, + &changed_dirs); + + // Don't send directory content change notification while performing + // the initial content retrieval. + const bool should_notify_directory_changed = (start_changestamp != 0); + if (should_notify_directory_changed) { + for (std::set<FilePath>::iterator dir_iter = changed_dirs.begin(); + dir_iter != changed_dirs.end(); ++dir_iter) { + FOR_EACH_OBSERVER(Observer, observers_, + OnDirectoryChanged(*dir_iter)); + } + } + + return error; +} + +} // namespace gdata diff --git a/chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.h b/chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.h new file mode 100644 index 0000000..f1f3d05 --- /dev/null +++ b/chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.h @@ -0,0 +1,206 @@ +// 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. + +#ifndef CHROME_BROWSER_CHROMEOS_GDATA_GDATA_WAPI_FEED_LOADER_H_ +#define CHROME_BROWSER_CHROMEOS_GDATA_GDATA_WAPI_FEED_LOADER_H_ + +#include "base/callback.h" +#include "base/file_path.h" +#include "base/observer_list.h" +#include "chrome/browser/chromeos/gdata/gdata_errorcode.h" +#include "chrome/browser/chromeos/gdata/gdata_files.h" + +namespace gdata { + +class DriveWebAppsRegistryInterface; +class DocumentsServiceInterface; +class GDataCache; +struct GetDocumentsUiState; + +// Set of parameters sent to LoadDocumentFeedCallback callback. +struct GetDocumentsParams { + GetDocumentsParams(int start_changestamp, + int root_feed_changestamp, + std::vector<DocumentFeed*>* feed_list, + bool should_fetch_multiple_feeds, + const FilePath& search_file_path, + const std::string& search_query, + const std::string& directory_resource_id, + const FindEntryCallback& callback, + GetDocumentsUiState* ui_state); + ~GetDocumentsParams(); + + // Changestamps are positive numbers in increasing order. The difference + // between two changestamps is proportional equal to number of items in + // delta feed between them - bigger the difference, more likely bigger + // number of items in delta feeds. + int start_changestamp; + int root_feed_changestamp; + scoped_ptr<std::vector<DocumentFeed*> > feed_list; + // Should we stop after getting first feed chunk, even if there is more + // data. + bool should_fetch_multiple_feeds; + FilePath search_file_path; + std::string search_query; + std::string directory_resource_id; + FindEntryCallback callback; + scoped_ptr<GetDocumentsUiState> ui_state; +}; + +// Callback run as a response to LoadFromServer. +typedef base::Callback<void(GetDocumentsParams* params, + GDataFileError error)> + LoadDocumentFeedCallback; + +// GDataWapiFeedLoader is used to load feeds from WAPI (codename for +// Documents List API) and load the cached proto file. +class GDataWapiFeedLoader { + public: + // Used to notify events from the loader. + // All events are notified on UI thread. + class Observer { + public: + // Triggered when a content of a directory has been changed. + // |directory_path| is a virtual directory path representing the + // changed directory. + virtual void OnDirectoryChanged(const FilePath& directory_path) {} + + // Triggered when a document feed is fetched. |num_accumulated_entries| + // tells the number of entries fetched so far. + virtual void OnDocumentFeedFetched(int num_accumulated_entries) {} + + // Triggered when the feed from the server is loaded. + virtual void OnFeedFromServerLoaded() {} + + protected: + virtual ~Observer() {} + }; + + GDataWapiFeedLoader( + GDataDirectoryService* directory_service, + DocumentsServiceInterface* documents_service, + DriveWebAppsRegistryInterface* webapps_registry, + GDataCache* cache, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_); + ~GDataWapiFeedLoader(); + + // Adds and removes the observer. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Starts root feed load from the cache. If successful, it will try to find + // the file upon retrieval completion. In addition to that, it will + // initiate retrieval of the root feed from the server unless + // |should_load_from_server| is set to false. |should_load_from_server| is + // false only for testing. + void LoadFromCache(bool should_load_from_server, + const FilePath& search_file_path, + const FindEntryCallback& callback); + + // Starts root feed load from the server. Value of |start_changestamp| + // determines the type of feed to load - 0 means root feed, every other + // value would trigger delta feed. + // In the case of loading the root feed we use |root_feed_changestamp| as its + // initial changestamp value since it does not come with that info. + // When done |load_feed_callback| is invoked. + // |entry_found_callback| is used only when this is invoked while searching + // for file info, and is used in |load_feed_callback|. If successful, it will + // try to find the file upon retrieval completion. + // |should_fetch_multiple_feeds| is true iff don't want to stop feed loading + // after we retrieve first feed chunk. + // If invoked as a part of content search, query will be set in + // |search_query|. + // If |feed_to_load| is set, this is feed url that will be used to load feed. + void LoadFromServer( + ContentOrigin initial_origin, + int start_changestamp, + int root_feed_changestamp, + bool should_fetch_multiple_feeds, + const FilePath& search_file_path, + const std::string& search_query, + const GURL& feed_to_load, + const std::string& directory_resource_id, + const FindEntryCallback& entry_found_callback, + const LoadDocumentFeedCallback& feed_load_callback); + + // Retrieves account metadata and determines from the last change timestamp + // if the feed content loading from the server needs to be initiated. + void ReloadFromServerIfNeeded( + ContentOrigin initial_origin, + int local_changestamp, + const FilePath& search_file_path, + const FindEntryCallback& callback); + + // Updates whole directory structure feeds collected in |feed_list|. + // On success, returns PLATFORM_FILE_OK. Record file statistics as UMA + // histograms. + // + // See comments at GDataWapiFeedProcessor::ApplyFeeds() for + // |start_changestamp| and |root_feed_changestamp|. + GDataFileError UpdateFromFeed( + const std::vector<DocumentFeed*>& feed_list, + int start_changestamp, + int root_feed_changestamp); + + private: + // Callback for handling root directory refresh from the cache. + void OnProtoLoaded(LoadRootFeedParams* params); + + // Continues handling root directory refresh after the directory service + // is fully loaded. + void ContinueWithInitializedDirectoryService(LoadRootFeedParams* params, + GDataFileError error); + + // Helper callback for handling results of metadata retrieval initiated from + // ReloadFeedFromServerIfNeeded(). This method makes a decision about fetching + // the content of the root feed during the root directory refresh process. + void OnGetAccountMetadata( + ContentOrigin initial_origin, + int local_changestamp, + const FilePath& search_file_path, + const FindEntryCallback& callback, + GDataErrorCode status, + scoped_ptr<base::Value> feed_data); + + // Callback for handling feed content fetching while searching for file info. + // This callback is invoked after async feed fetch operation that was + // invoked by StartDirectoryRefresh() completes. This callback will update + // the content of the refreshed directory object and continue initially + // started FindEntryByPath() request. + void OnFeedFromServerLoaded(GetDocumentsParams* params, + GDataFileError error); + + // Callback for handling response from |GDataDocumentsService::GetDocuments|. + // Invokes |callback| when done. + void OnGetDocuments( + ContentOrigin initial_origin, + const LoadDocumentFeedCallback& callback, + GetDocumentsParams* params, + base::TimeTicks start_time, + GDataErrorCode status, + scoped_ptr<base::Value> data); + + // Save filesystem to disk. + void SaveFileSystem(); + + // Callback for handling UI updates caused by document fetching. + void OnNotifyDocumentFeedFetched( + base::WeakPtr<GetDocumentsUiState> ui_state); + + GDataDirectoryService* directory_service_; // Not owned. + DocumentsServiceInterface* documents_service_; // Not owned. + DriveWebAppsRegistryInterface* webapps_registry_; // Not owned. + GDataCache* cache_; // Not owned. + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; + ObserverList<Observer> observers_; + + // Note: This should remain the last member so it'll be destroyed and + // invalidate its weak pointers before any other members are destroyed. + base::WeakPtrFactory<GDataWapiFeedLoader> weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(GDataWapiFeedLoader); +}; + +} // namespace gdata + +#endif // CHROME_BROWSER_CHROMEOS_GDATA_GDATA_WAPI_FEED_LOADER_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 64753f1..6322452 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -593,6 +593,8 @@ 'browser/chromeos/gdata/gdata_uploader.h', 'browser/chromeos/gdata/gdata_util.cc', 'browser/chromeos/gdata/gdata_util.h', + 'browser/chromeos/gdata/gdata_wapi_feed_loader.cc', + 'browser/chromeos/gdata/gdata_wapi_feed_loader.h', 'browser/chromeos/gdata/gdata_wapi_feed_processor.cc', 'browser/chromeos/gdata/gdata_wapi_feed_processor.h', 'browser/chromeos/gdata/gdata_wapi_parser.cc', |