| Index: chrome/browser/chromeos/gdata/gdata_wapi_feed_loader.cc
|
| 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 0000000000000000000000000000000000000000..b5eb7cdcbf426b286711427ae47800547a1b2982
|
| --- /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
|
|
|