Index: chrome/browser/chromeos/gdata/gdata_files.cc |
=================================================================== |
--- chrome/browser/chromeos/gdata/gdata_files.cc (revision 149697) |
+++ chrome/browser/chromeos/gdata/gdata_files.cc (working copy) |
@@ -4,19 +4,25 @@ |
#include "chrome/browser/chromeos/gdata/gdata_files.h" |
+#include <leveldb/db.h> |
#include <vector> |
#include "base/message_loop_proxy.h" |
#include "base/platform_file.h" |
+#include "base/string_number_conversions.h" |
#include "base/string_util.h" |
#include "base/stringprintf.h" |
+#include "base/sequenced_task_runner.h" |
#include "base/tracked_objects.h" |
#include "base/utf_string_conversions.h" |
#include "chrome/browser/chromeos/gdata/gdata.pb.h" |
#include "chrome/browser/chromeos/gdata/gdata_util.h" |
#include "chrome/browser/chromeos/gdata/gdata_wapi_parser.h" |
+#include "content/public/browser/browser_thread.h" |
#include "net/base/escape.h" |
+using content::BrowserThread; |
+ |
namespace gdata { |
namespace { |
@@ -423,12 +429,126 @@ |
child_directories_.clear(); |
} |
+// GDataDirectoryServiceDB implementation. |
+ |
+// Params for GDatadirectoryServiceDB::Create. |
+struct CreateDBParams { |
+ CreateDBParams(const FilePath& db_path, |
+ base::SequencedTaskRunner* blocking_task_runner) |
+ : db_path(db_path), |
+ blocking_task_runner(blocking_task_runner) { |
+ } |
+ |
+ FilePath db_path; |
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner; |
+ scoped_ptr<GDataDirectoryServiceDB> db; |
+ GDataDirectoryService::SerializedMap serialized_resources; |
+}; |
+ |
+// Wrapper for level db. All methods must be called on blocking thread. |
+class GDataDirectoryServiceDB { |
satorux1
2012/08/03 06:32:50
From this name, I originally thought this is a sub
achuithb
2012/08/03 19:15:59
I like ResourceMetadataDB. I've done the rename.
|
+ public: |
+ GDataDirectoryServiceDB(const FilePath& db_path, |
+ base::SequencedTaskRunner* blocking_task_runner); |
+ |
+ // Initializes the database. |
+ void Init(); |
+ |
+ // Reads the database into |serialized_resources|. |
+ void Read(GDataDirectoryService::SerializedMap* serialized_resources); |
+ |
+ // Saves |serialized_resources| to the database. |
+ void Save(const GDataDirectoryService::SerializedMap& serialized_resources); |
+ |
+ private: |
+ // Clears the database. |
+ void Clear(); |
+ |
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; |
+ scoped_ptr<leveldb::DB> level_db_; |
+ FilePath db_path_; |
+}; |
+ |
+GDataDirectoryServiceDB::GDataDirectoryServiceDB(const FilePath& db_path, |
+ base::SequencedTaskRunner* blocking_task_runner) |
+ : blocking_task_runner_(blocking_task_runner), |
+ db_path_(db_path) { |
+ DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); |
+} |
+ |
+// Creates, initializes and reads from the database. |
+// This must be defined after GDataDirectoryServiceDB and CreateDBParams. |
+static void CreateGDataDirectoryServiceDBOnBlockingPool( |
+ CreateDBParams* params) { |
+ DCHECK(params->blocking_task_runner->RunsTasksOnCurrentThread()); |
+ DCHECK(!params->db_path.empty()); |
+ |
+ params->db.reset(new GDataDirectoryServiceDB(params->db_path, |
+ params->blocking_task_runner)); |
+ params->db->Init(); |
+ params->db->Read(¶ms->serialized_resources); |
+} |
+ |
+void GDataDirectoryServiceDB::Init() { |
+ DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); |
+ DCHECK(!db_path_.empty()); |
+ |
+ DVLOG(1) << "Init " << db_path_.value(); |
+ |
+ leveldb::DB* level_db = NULL; |
+ leveldb::Options options; |
+ options.create_if_missing = true; |
+ leveldb::Status db_status = leveldb::DB::Open(options, db_path_.value(), |
+ &level_db); |
+ DCHECK(level_db); |
+ DCHECK(db_status.ok()); |
+ level_db_.reset(level_db); |
+} |
+ |
+void GDataDirectoryServiceDB::Read( |
+ GDataDirectoryService::SerializedMap* serialized_resources) { |
+ DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); |
+ DCHECK(serialized_resources); |
+ DVLOG(1) << "Read " << db_path_.value(); |
+ |
+ scoped_ptr<leveldb::Iterator> iter(level_db_->NewIterator( |
+ leveldb::ReadOptions())); |
+ for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
+ DVLOG(1) << "Read, resource " << iter->key().ToString(); |
+ serialized_resources->insert(std::make_pair(iter->key().ToString(), |
+ iter->value().ToString())); |
+ } |
+} |
+ |
+void GDataDirectoryServiceDB::Save( |
+ const GDataDirectoryService::SerializedMap& serialized_resources) { |
+ DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); |
+ |
+ Clear(); |
+ for (GDataDirectoryService::SerializedMap::const_iterator iter = |
+ serialized_resources.begin(); |
+ iter != serialized_resources.end(); ++iter) { |
+ DVLOG(1) << "Saving resource " << iter->first << " to db"; |
+ level_db_->Put(leveldb::WriteOptions(), |
satorux1
2012/08/03 06:32:50
What if Put fails? Shouldn't we check the return v
achuithb
2012/08/03 19:15:59
I've added a LOG(error), but I'm not sure what els
|
+ leveldb::Slice(iter->first), |
+ leveldb::Slice(iter->second)); |
+ } |
+} |
+ |
+void GDataDirectoryServiceDB::Clear() { |
+ level_db_.reset(); |
+ leveldb::DestroyDB(db_path_.value(), leveldb::Options()); |
+ Init(); |
+} |
+ |
// GDataDirectoryService class implementation. |
GDataDirectoryService::GDataDirectoryService() |
- : serialized_size_(0), |
+ : blocking_task_runner_(NULL), |
+ serialized_size_(0), |
largest_changestamp_(0), |
- origin_(UNINITIALIZED) { |
+ origin_(UNINITIALIZED), |
+ weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { |
root_.reset(new GDataDirectory(NULL, this)); |
root_->set_title(kGDataRootDirectory); |
root_->SetBaseNameFromTitle(); |
@@ -437,12 +557,23 @@ |
} |
GDataDirectoryService::~GDataDirectoryService() { |
+ ClearRoot(); |
+ |
+ // Ensure db is closed on the blocking pool. |
+ if (blocking_task_runner_ && directory_service_db_.get()) |
+ blocking_task_runner_->PostTask(FROM_HERE, |
satorux1
2012/08/03 06:32:50
I think you can use DelteSoon() instead
achuithb
2012/08/03 19:15:59
Done.
|
+ base::Bind(&base::DeletePointer<GDataDirectoryServiceDB>, |
+ directory_service_db_.release())); |
+} |
+ |
+void GDataDirectoryService::ClearRoot() { |
// Note that children have a reference to root_, |
// so we need to delete them here. |
root_->RemoveChildren(); |
RemoveEntryFromResourceMap(root_.get()); |
DCHECK(resource_map_.empty()); |
resource_map_.clear(); |
+ root_.reset(); |
} |
void GDataDirectoryService::AddEntryToDirectory( |
@@ -561,6 +692,159 @@ |
} |
} |
+void GDataDirectoryService::InitFromDB( |
+ const FilePath& db_path, |
+ base::SequencedTaskRunner* blocking_task_runner, |
+ LoadRootFeedParams* load_params, |
+ const base::Closure& callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ DCHECK(!db_path.empty()); |
+ DCHECK(blocking_task_runner); |
+ |
+ if (directory_service_db_.get()) { |
+ NOTREACHED(); |
+ if (!callback.is_null()) |
+ callback.Run(); |
+ } |
+ |
+ blocking_task_runner_ = blocking_task_runner; |
+ |
+ DVLOG(1) << "InitFromDB " << db_path.value(); |
+ |
+ CreateDBParams* create_params = |
+ new CreateDBParams(db_path, blocking_task_runner); |
+ blocking_task_runner_->PostTaskAndReply( |
+ FROM_HERE, |
+ base::Bind(&CreateGDataDirectoryServiceDBOnBlockingPool, |
+ create_params), |
+ base::Bind(&GDataDirectoryService::InitResourceMap, |
+ weak_ptr_factory_.GetWeakPtr(), |
+ base::Owned(create_params), |
+ load_params, |
+ callback)); |
+} |
+ |
+void GDataDirectoryService::InitResourceMap( |
+ CreateDBParams* create_params, |
+ LoadRootFeedParams* load_params, |
+ const base::Closure& callback) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ DCHECK(create_params); |
+ // Ensure this callback is run before we return. |
+ base::ScopedClosureRunner callback_runner(callback); |
+ |
+ directory_service_db_ = create_params->db.Pass(); |
+ if (create_params->serialized_resources.empty()) { |
+ origin_ = INITIALIZING; |
+ if (load_params) |
+ load_params->load_error = GDATA_FILE_ERROR_NOT_FOUND; |
+ return; |
+ } |
+ |
+ ClearRoot(); |
+ |
+ int32 version = 0; |
+ ResourceMap resource_map; |
+ for (SerializedMap::const_iterator iter = |
+ create_params->serialized_resources.begin(); |
satorux1
2012/08/03 06:32:50
indentation
achuithb
2012/08/03 19:15:59
Done.
|
+ iter != create_params->serialized_resources.end(); ++iter) { |
+ if (iter->first == "version") { |
+ if (!base::StringToInt(iter->second, &version) || |
+ version != kProtoVersion) { |
satorux1
2012/08/03 06:32:50
shouldn't we set the load_error to GDATA_FILE_ERRO
achuithb
2012/08/03 19:15:59
Done.
|
+ return; |
+ } |
+ continue; |
+ } |
+ |
+ if (iter->first == "largest_changestamp") { |
+ base::StringToInt(iter->second, &largest_changestamp_); |
+ DVLOG(1) << "InitResourceMap largest_changestamp_" |
+ << largest_changestamp_; |
satorux1
2012/08/03 06:32:50
ditto. load_error?
achuithb
2012/08/03 19:15:59
I think this is an error but it's not fatal, right
|
+ continue; |
+ } |
+ |
+ scoped_ptr<GDataEntry> entry = FromProtoString(iter->second); |
+ if (entry.get()) { |
+ DVLOG(1) << "Inserting resource " << iter->first |
+ << " into resource_map"; |
+ resource_map.insert(std::make_pair(iter->first, entry.release())); |
+ } else { |
+ NOTREACHED() << "Failed to parse GDataEntry for resource " |
+ << iter->first; |
+ } |
+ } |
+ |
+ // Fix up parent-child relations. |
+ for (ResourceMap::iterator iter = resource_map.begin(); |
+ iter != resource_map.end(); ++iter) { |
+ GDataEntry* entry = iter->second; |
+ ResourceMap::iterator parent_it = |
+ resource_map.find(entry->parent_resource_id()); |
+ if (parent_it != resource_map.end()) { |
+ GDataDirectory* parent = parent_it->second->AsGDataDirectory(); |
+ if (parent) { |
+ DVLOG(1) << "Adding " << entry->resource_id() |
+ << " as a child of " << parent->resource_id(); |
+ parent->AddEntry(entry); |
+ } else { |
+ NOTREACHED() << "Parent is not a directory " << parent->resource_id(); |
+ } |
+ } else if (entry->resource_id() == kGDataRootDirectoryResourceId) { |
+ root_.reset(entry->AsGDataDirectory()); |
+ DCHECK(root_.get()); |
+ AddEntryToResourceMap(root_.get()); |
+ } else { |
+ NOTREACHED() << "Missing parent id " << entry->parent_resource_id() |
+ << " for resource " << entry->resource_id(); |
+ } |
+ } |
+ |
+ DCHECK(root_.get()); |
+ DCHECK_EQ(resource_map.size(), resource_map_.size()); |
+ DCHECK_EQ(resource_map.size(), |
+ create_params->serialized_resources.size() - 2); |
+ |
+ origin_ = FROM_CACHE; |
+} |
+ |
+void GDataDirectoryService::SaveToDB() { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ |
+ if (!blocking_task_runner_ || !directory_service_db_.get()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ size_t serialized_size = 0; |
+ SerializedMap serialized_resources; |
+ for (ResourceMap::const_iterator iter = resource_map_.begin(); |
+ iter != resource_map_.end(); ++iter) { |
+ GDataEntryProto proto; |
+ iter->second->ToProtoFull(&proto); |
+ std::string serialized_string; |
+ const bool ok = proto.SerializeToString(&serialized_string); |
+ DCHECK(ok); |
+ if (ok) { |
+ serialized_resources.insert( |
+ std::make_pair(iter->first, serialized_string)); |
+ serialized_size += serialized_string.size(); |
+ } |
+ } |
+ |
+ serialized_resources.insert(std::make_pair("version", |
+ base::IntToString(kProtoVersion))); |
+ serialized_resources.insert(std::make_pair("largest_changestamp", |
satorux1
2012/08/03 06:32:50
Let's define constants for "version" and "largest_
achuithb
2012/08/03 19:15:59
Done.
|
+ base::IntToString(largest_changestamp_))); |
+ set_last_serialized(base::Time::Now()); |
+ set_serialized_size(serialized_size); |
+ |
+ blocking_task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&GDataDirectoryServiceDB::Save, |
+ base::Unretained(directory_service_db_.get()), |
+ serialized_resources)); |
+} |
+ |
// Convert to/from proto. |
// static |
@@ -795,4 +1079,31 @@ |
return true; |
} |
+scoped_ptr<GDataEntry> GDataDirectoryService::FromProtoString( |
+ const std::string& serialized_proto) { |
+ scoped_ptr<GDataEntry> entry; |
+ |
+ GDataEntryProto entry_proto; |
+ if (!entry_proto.ParseFromString(serialized_proto)) |
+ return entry.Pass(); |
hashimoto
2012/08/03 05:17:07
Why don't you simply pass scoped_ptr<GDataEntry>()
satorux1
2012/08/03 06:32:50
Agreed with hashimoto. That'll be clearer
achuithb
2012/08/03 19:15:59
Done.
|
+ |
+ if (entry_proto.file_info().is_directory()) { |
satorux1
2012/08/03 06:32:50
move GDataEntryProto entry_proto; right before 'if
achuithb
2012/08/03 19:15:59
I don't understand? It's needed for ParseFromStrin
satorux1
2012/08/03 20:19:16
my bad. i was wrong.
|
+ entry.reset(new GDataDirectory(NULL, this)); |
+ // Call GDataEntry::FromProto. |
satorux1
2012/08/03 06:32:50
Please add some more comment that why GDtaDirector
achuithb
2012/08/03 19:15:59
Correct. Comment added.
|
+ if (!entry->FromProto(entry_proto)) { |
+ NOTREACHED() << "FromProto (directory) failed"; |
+ entry.reset(); |
+ } |
+ } else { |
+ scoped_ptr<GDataFile> file(new GDataFile(NULL, this)); |
+ // Call GDataFile::FromProto. |
+ if (file->FromProto(entry_proto)) { |
+ entry.reset(file.release()); |
+ } else { |
+ NOTREACHED() << "FromProto (file) failed"; |
+ } |
+ } |
+ return entry.Pass(); |
+} |
+ |
} // namespace gdata |