drive: Stop using DictionaryValue to store data in FakeDriveService


Replace the giant DictionaryValue with std::map.
No behavior change is intended.

Remove unused FakeDriveService::FindEntryByContentUrl().
Fix util::ConvertResourceEntryToFileResource() to set EntryKind of hosted documents correctly.
Stop depending on the results' order in SearchOperationTest.

BUG=334544
TEST=unit_tests

Review URL: https://codereview.chromium.org/135643005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@245487 0039d316-1c4b-4281-b951-d872f2087c98
parent 858d789c
......@@ -39,6 +39,25 @@ const char kGoogleTableMimeType[] = "application/vnd.google-apps.table";
const char kGoogleFormMimeType[] = "application/vnd.google-apps.form";
const char kDriveFolderMimeType[] = "application/vnd.google-apps.folder";
std::string GetMimeTypeFromEntryKind(google_apis::DriveEntryKind kind) {
switch (kind) {
case google_apis::ENTRY_KIND_DOCUMENT:
return kGoogleDocumentMimeType;
case google_apis::ENTRY_KIND_SPREADSHEET:
return kGoogleSpreadsheetMimeType;
case google_apis::ENTRY_KIND_PRESENTATION:
return kGooglePresentationMimeType;
case google_apis::ENTRY_KIND_DRAWING:
return kGoogleDrawingMimeType;
case google_apis::ENTRY_KIND_TABLE:
return kGoogleTableMimeType;
case google_apis::ENTRY_KIND_FORM:
return kGoogleFormMimeType;
default:
return std::string();
}
}
ScopedVector<std::string> CopyScopedVectorString(
const ScopedVector<std::string>& source) {
ScopedVector<std::string> result;
......@@ -323,10 +342,14 @@ scoped_ptr<google_apis::FileResource> ConvertResourceEntryToFileResource(
"shared") != entry.labels().end());
file->set_download_url(entry.download_url());
if (entry.is_folder())
if (entry.is_folder()) {
file->set_mime_type(kDriveFolderMimeType);
else
file->set_mime_type(entry.content_mime_type());
} else {
std::string mime_type = GetMimeTypeFromEntryKind(entry.kind());
if (mime_type.empty())
mime_type = entry.content_mime_type();
file->set_mime_type(mime_type);
}
file->set_md5_checksum(entry.file_md5());
file->set_file_size(entry.file_size());
......
......@@ -34,8 +34,10 @@ using google_apis::AppListCallback;
using google_apis::AuthStatusCallback;
using google_apis::AuthorizeAppCallback;
using google_apis::CancelCallback;
using google_apis::ChangeResource;
using google_apis::DownloadActionCallback;
using google_apis::EntryActionCallback;
using google_apis::FileResource;
using google_apis::GDATA_FILE_ERROR;
using google_apis::GDATA_NO_CONNECTION;
using google_apis::GDATA_OTHER_ERROR;
......@@ -53,6 +55,7 @@ using google_apis::HTTP_RESUME_INCOMPLETE;
using google_apis::HTTP_SUCCESS;
using google_apis::InitiateUploadCallback;
using google_apis::Link;
using google_apis::ParentReference;
using google_apis::ProgressCallback;
using google_apis::ResourceEntry;
using google_apis::ResourceList;
......@@ -63,13 +66,8 @@ namespace test_util = google_apis::test_util;
namespace drive {
namespace {
// Rel property of an upload link in the entries dictionary value.
const char kUploadUrlRel[] =
"http://schemas.google.com/g/2005#resumable-create-media";
// Rel property of a share link in the entries dictionary value.
const char kShareUrlRel[] =
"http://schemas.google.com/docs/2007#share";
// Mime type of directories.
const char kDriveFolderMimeType[] = "application/vnd.google-apps.folder";
// Returns true if a resource entry matches with the search query.
// Supports queries consist of following format.
......@@ -126,24 +124,14 @@ void EntryActionCallbackAdapter(
callback.Run(error);
}
void ClearPropatiesForPermanentDelete(base::DictionaryValue* entry) {
scoped_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue);
const char* kPreservedProperties[] = {
"docs$removed", "docs$changestamp", "gd$resourceId", "id", "updated"
};
for (size_t i = 0; i < arraysize(kPreservedProperties); ++i) {
const char* key = kPreservedProperties[i];
scoped_ptr<base::Value> value;
if (entry->Remove(key, &value))
new_entry->Set(key, value.release());
}
entry->Swap(new_entry.get());
}
} // namespace
struct FakeDriveService::EntryInfo {
google_apis::ChangeResource change_resource;
GURL share_url;
std::string content_data;
};
struct FakeDriveService::UploadSession {
std::string content_type;
int64 content_length;
......@@ -194,6 +182,7 @@ FakeDriveService::FakeDriveService()
FakeDriveService::~FakeDriveService() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
STLDeleteValues(&entries_);
}
bool FakeDriveService::LoadResourceListForWapi(
......@@ -209,26 +198,40 @@ bool FakeDriveService::LoadResourceListForWapi(
if (raw_value->GetAsDictionary(&as_dict) &&
as_dict->Remove("feed", &feed) &&
feed->GetAsDictionary(&feed_as_dict)) {
ignore_result(feed.release());
resource_list_value_.reset(feed_as_dict);
// Go through entries and convert test$data from a string to a binary blob.
base::ListValue* entries = NULL;
if (feed_as_dict->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
std::string content_data;
if (entries->GetDictionary(i, &entry) &&
entry->GetString("test$data", &content_data)) {
entry->Set("test$data",
base::BinaryValue::CreateWithCopiedBuffer(
content_data.c_str(), content_data.size()));
if (entries->GetDictionary(i, &entry)) {
scoped_ptr<ResourceEntry> resource_entry =
ResourceEntry::CreateFrom(*entry);
const std::string resource_id = resource_entry->resource_id();
EntryInfoMap::iterator it = entries_.find(resource_id);
if (it == entries_.end()) {
it = entries_.insert(
std::make_pair(resource_id, new EntryInfo)).first;
}
EntryInfo* new_entry = it->second;
ChangeResource* change = &new_entry->change_resource;
change->set_change_id(resource_entry->changestamp());
change->set_file_id(resource_id);
change->set_file(
util::ConvertResourceEntryToFileResource(*resource_entry));
const Link* share_url =
resource_entry->GetLinkByType(Link::LINK_SHARE);
if (share_url)
new_entry->share_url = share_url->href();
entry->GetString("test$data", &new_entry->content_data);
}
}
}
}
return resource_list_value_;
return feed_as_dict;
}
bool FakeDriveService::LoadAccountMetadataForWapi(
......@@ -248,18 +251,8 @@ bool FakeDriveService::LoadAccountMetadataForWapi(
// Add the largest changestamp to the existing entries.
// This will be used to generate change lists in GetResourceList().
if (resource_list_value_) {
base::ListValue* entries = NULL;
if (resource_list_value_->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
if (entries->GetDictionary(i, &entry)) {
entry->SetString("docs$changestamp.value",
base::Int64ToString(largest_changestamp_));
}
}
}
}
for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end(); ++it)
it->second->change_resource.set_change_id(largest_changestamp_);
return account_metadata_value_;
}
......@@ -465,10 +458,10 @@ CancelCallback FakeDriveService::GetResourceEntry(
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
scoped_ptr<ResourceEntry> resource_entry =
ResourceEntry::CreateFrom(*entry);
util::ConvertChangeResourceToResourceEntry(entry->change_resource);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
......@@ -499,26 +492,14 @@ CancelCallback FakeDriveService::GetShareUrl(
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
// Share urls are stored in the resource entry, and they do not rely on the
// embedding origin.
scoped_ptr<ResourceEntry> resource_entry =
ResourceEntry::CreateFrom(*entry);
const Link* share_url = resource_entry->GetLinkByType(Link::LINK_SHARE);
if (share_url) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_SUCCESS, share_url->href()));
} else {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_SUCCESS, GURL()));
}
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_SUCCESS, entry->share_url));
return CancelCallback();
}
scoped_ptr<ResourceEntry> null;
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_NOT_FOUND, GURL()));
......@@ -589,36 +570,28 @@ CancelCallback FakeDriveService::DeleteResource(
return CancelCallback();
}
base::ListValue* entries = NULL;
if (resource_list_value_->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
std::string current_resource_id;
if (entries->GetDictionary(i, &entry) &&
entry->GetString("gd$resourceId.$t", &current_resource_id) &&
resource_id == current_resource_id) {
if (entry->HasKey("docs$removed")) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
return CancelCallback();
}
std::string entry_etag;
entry->GetString("gd$etag", &entry_etag);
if (!etag.empty() && entry_etag != etag) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
return CancelCallback();
}
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
ChangeResource* change = &entry->change_resource;
const FileResource* file = change->file();
if (change->is_deleted()) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
return CancelCallback();
}
entry->Set("docs$removed", new base::DictionaryValue);
AddNewChangestamp(entry);
ClearPropatiesForPermanentDelete(entry);
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
return CancelCallback();
}
if (!etag.empty() && etag != file->etag()) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
return CancelCallback();
}
change->set_deleted(true);
AddNewChangestamp(change);
change->set_file(scoped_ptr<FileResource>());
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
return CancelCallback();
}
base::MessageLoop::current()->PostTask(
......@@ -638,28 +611,21 @@ CancelCallback FakeDriveService::TrashResource(
return CancelCallback();
}
base::ListValue* entries = NULL;
// Go through entries and remove the one that matches |resource_id|.
if (resource_list_value_->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
std::string current_resource_id;
if (entries->GetDictionary(i, &entry) &&
entry->GetString("gd$resourceId.$t", &current_resource_id) &&
resource_id == current_resource_id) {
GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
if (entry->HasKey("gd$deleted") || entry->HasKey("docs$removed")) {
error = HTTP_NOT_FOUND;
} else {
entry->Set("gd$deleted", new base::DictionaryValue);
AddNewChangestamp(entry);
error = HTTP_SUCCESS;
}
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, error));
return CancelCallback();
}
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
ChangeResource* change = &entry->change_resource;
FileResource* file = change->mutable_file();
GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
if (change->is_deleted() || file->labels().is_trashed()) {
error = HTTP_NOT_FOUND;
} else {
file->mutable_labels()->set_trashed(true);
AddNewChangestamp(change);
error = HTTP_SUCCESS;
}
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, error));
return CancelCallback();
}
base::MessageLoop::current()->PostTask(
......@@ -685,8 +651,7 @@ CancelCallback FakeDriveService::DownloadFile(
return CancelCallback();
}
// The field content.src is the URL to download the file.
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (!entry) {
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
......@@ -694,51 +659,42 @@ CancelCallback FakeDriveService::DownloadFile(
return CancelCallback();
}
// Write "x"s of the file size specified in the entry.
std::string file_size_string;
entry->GetString("docs$size.$t", &file_size_string);
int64 file_size = 0;
if (base::StringToInt64(file_size_string, &file_size)) {
base::BinaryValue* content_binary_data;
std::string content_data;
if (entry->GetBinary("test$data", &content_binary_data)) {
content_data = std::string(content_binary_data->GetBuffer(),
content_binary_data->GetSize());
}
DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
if (!get_content_callback.is_null()) {
const int64 kBlockSize = 5;
for (int64 i = 0; i < file_size; i += kBlockSize) {
const int64 size = std::min(kBlockSize, file_size - i);
scoped_ptr<std::string> content_for_callback(
new std::string(content_data.substr(i, size)));
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(get_content_callback, HTTP_SUCCESS,
base::Passed(&content_for_callback)));
}
const FileResource* file = entry->change_resource.file();
const std::string& content_data = entry->content_data;
int64 file_size = file->file_size();
DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
if (!get_content_callback.is_null()) {
const int64 kBlockSize = 5;
for (int64 i = 0; i < file_size; i += kBlockSize) {
const int64 size = std::min(kBlockSize, file_size - i);
scoped_ptr<std::string> content_for_callback(
new std::string(content_data.substr(i, size)));
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(get_content_callback, HTTP_SUCCESS,
base::Passed(&content_for_callback)));
}
}
if (test_util::WriteStringToFile(local_cache_path, content_data)) {
if (!progress_callback.is_null()) {
// See also the comment in ResumeUpload(). For testing that clients
// can handle the case progress_callback is called multiple times,
// here we invoke the callback twice.
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(progress_callback, file_size / 2, file_size));
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(progress_callback, file_size, file_size));
}
if (test_util::WriteStringToFile(local_cache_path, content_data)) {
if (!progress_callback.is_null()) {
// See also the comment in ResumeUpload(). For testing that clients
// can handle the case progress_callback is called multiple times,
// here we invoke the callback twice.
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(download_action_callback,
HTTP_SUCCESS,
local_cache_path));
return CancelCallback();
base::Bind(progress_callback, file_size / 2, file_size));
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(progress_callback, file_size, file_size));
}
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(download_action_callback,
HTTP_SUCCESS,
local_cache_path));
return CancelCallback();
}
// Failed to write the content.
......@@ -770,57 +726,51 @@ CancelCallback FakeDriveService::CopyResource(
const std::string& parent_resource_id = in_parent_resource_id.empty() ?
GetRootResourceId() : in_parent_resource_id;
base::ListValue* entries = NULL;
// Go through entries and copy the one that matches |resource_id|.
if (resource_list_value_->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
std::string current_resource_id;
if (entries->GetDictionary(i, &entry) &&
entry->GetString("gd$resourceId.$t", &current_resource_id) &&
resource_id == current_resource_id) {
// Make a copy and set the new resource ID and the new title.
scoped_ptr<base::DictionaryValue> copied_entry(entry->DeepCopy());
copied_entry->SetString("gd$resourceId.$t", GetNewResourceId());
copied_entry->SetString("title.$t", new_title);
// Reset parent directory.
base::ListValue* links = NULL;
if (!copied_entry->GetList("link", &links)) {
links = new base::ListValue;
copied_entry->Set("link", links);
}
links->Clear();
base::DictionaryValue* link = new base::DictionaryValue;
link->SetString(
"rel", "http://schemas.google.com/docs/2007#parent");
link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec());
links->Append(link);
if (!last_modified.is_null()) {
copied_entry->SetString(
"updated.$t",
google_apis::util::FormatTimeAsString(last_modified));
}
AddNewChangestamp(copied_entry.get());
UpdateETag(copied_entry.get());
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
// Make a copy and set the new resource ID and the new title.
scoped_ptr<EntryInfo> copied_entry(new EntryInfo);
copied_entry->content_data = entry->content_data;
copied_entry->share_url = entry->share_url;
// TODO(hashimoto): Implement a proper way to copy FileResource.
scoped_ptr<ResourceEntry> copied_resource_entry =
util::ConvertChangeResourceToResourceEntry(entry->change_resource);
copied_entry->change_resource.set_file(
util::ConvertResourceEntryToFileResource(*copied_resource_entry));
ChangeResource* new_change = &copied_entry->change_resource;
FileResource* new_file = new_change->mutable_file();
const std::string new_resource_id = GetNewResourceId();
new_change->set_file_id(new_resource_id);
new_file->set_file_id(new_resource_id);
new_file->set_title(new_title);
scoped_ptr<ParentReference> parent(new ParentReference);
parent->set_file_id(parent_resource_id);
parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
parent->set_is_root(parent_resource_id == GetRootResourceId());
ScopedVector<ParentReference> parents;
parents.push_back(parent.release());
new_file->set_parents(parents.Pass());
if (!last_modified.is_null())
new_file->set_modified_date(last_modified);
AddNewChangestamp(new_change);
UpdateETag(new_file);
// Parse the new entry.
scoped_ptr<ResourceEntry> resource_entry =
ResourceEntry::CreateFrom(*copied_entry);
// Add it to the resource list.
entries->Append(copied_entry.release());
scoped_ptr<ResourceEntry> resource_entry =
util::ConvertChangeResourceToResourceEntry(*new_change);
// Add the new entry to the map.
entries_[new_resource_id] = copied_entry.release();
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback,
HTTP_SUCCESS,
base::Passed(&resource_entry)));
return CancelCallback();
}
}
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback,
HTTP_SUCCESS,
base::Passed(&resource_entry)));
return CancelCallback();
}
scoped_ptr<ResourceEntry> null;
......@@ -847,58 +797,35 @@ CancelCallback FakeDriveService::UpdateResource(
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
entry->SetString("title.$t", new_title);
ChangeResource* change = &entry->change_resource;
FileResource* file = change->mutable_file();
file->set_title(new_title);
// Set parent if necessary.
if (!parent_resource_id.empty()) {
base::ListValue* links = NULL;
if (!entry->GetList("link", &links)) {
links = new base::ListValue;
entry->Set("link", links);
}
// Remove old parent(s).
for (size_t i = 0; i < links->GetSize(); ) {
base::DictionaryValue* link = NULL;
std::string rel;
std::string href;
if (links->GetDictionary(i, &link) &&
link->GetString("rel", &rel) &&
link->GetString("href", &href) &&
rel == "http://schemas.google.com/docs/2007#parent") {
links->Remove(i, NULL);
} else {
++i;
}
}
base::DictionaryValue* link = new base::DictionaryValue;
link->SetString("rel", "http://schemas.google.com/docs/2007#parent");
link->SetString(
"href", GetFakeLinkUrl(parent_resource_id).spec());
links->Append(link);
scoped_ptr<ParentReference> parent(new ParentReference);
parent->set_file_id(parent_resource_id);
parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
parent->set_is_root(parent_resource_id == GetRootResourceId());
ScopedVector<ParentReference> parents;
parents.push_back(parent.release());
file->set_parents(parents.Pass());
}
if (!last_modified.is_null()) {
entry->SetString(
"updated.$t",
google_apis::util::FormatTimeAsString(last_modified));
}
if (!last_modified.is_null())
file->set_modified_date(last_modified);
if (!last_viewed_by_me.is_null()) {
entry->SetString(
"gd$lastViewed.$t",
google_apis::util::FormatTimeAsString(last_viewed_by_me));
}
if (!last_viewed_by_me.is_null())
file->set_last_viewed_by_me_date(last_viewed_by_me);
AddNewChangestamp(entry);
UpdateETag(entry);
AddNewChangestamp(change);
UpdateETag(file);
// Parse the new entry.
scoped_ptr<ResourceEntry> resource_entry =
ResourceEntry::CreateFrom(*entry);
util::ConvertChangeResourceToResourceEntry(*change);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_SUCCESS, base::Passed(&resource_entry)));
......@@ -937,25 +864,20 @@ CancelCallback FakeDriveService::AddResourceToDirectory(
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
base::ListValue* links = NULL;
if (!entry->GetList("link", &links)) {
links = new base::ListValue;
entry->Set("link", links);
}
ChangeResource* change = &entry->change_resource;
// On the real Drive server, resources do not necessary shape a tree
// structure. That is, each resource can have multiple parent.
// We mimic the behavior here; AddResourceToDirectoy just adds
// one more parent link, not overwriting old links.
base::DictionaryValue* link = new base::DictionaryValue;
link->SetString("rel", "http://schemas.google.com/docs/2007#parent");
link->SetString(
"href", GetFakeLinkUrl(parent_resource_id).spec());
links->Append(link);
AddNewChangestamp(entry);
// one more parent, not overwriting old ones.
scoped_ptr<ParentReference> parent(new ParentReference);
parent->set_file_id(parent_resource_id);
parent->set_parent_link(GetFakeLinkUrl(parent_resource_id));
parent->set_is_root(parent_resource_id == GetRootResourceId());
change->mutable_file()->mutable_parents()->push_back(parent.release());
AddNewChangestamp(change);
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
return CancelCallback();
......@@ -979,26 +901,18 @@ CancelCallback FakeDriveService::RemoveResourceFromDirectory(
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (entry) {
base::ListValue* links = NULL;
if (entry->GetList("link", &links)) {
GURL parent_content_url = GetFakeLinkUrl(parent_resource_id);
for (size_t i = 0; i < links->GetSize(); ++i) {
base::DictionaryValue* link = NULL;
std::string rel;
std::string href;
if (links->GetDictionary(i, &link) &&
link->GetString("rel", &rel) &&
link->GetString("href", &href) &&
rel == "http://schemas.google.com/docs/2007#parent" &&
GURL(href) == parent_content_url) {
links->Remove(i, NULL);
AddNewChangestamp(entry);
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
return CancelCallback();
}
ChangeResource* change = &entry->change_resource;
FileResource* file = change->mutable_file();
ScopedVector<ParentReference>* parents = file->mutable_parents();
for (size_t i = 0; i < parents->size(); ++i) {
if ((*parents)[i]->file_id() == parent_resource_id) {
parents->erase(parents->begin() + i);
AddNewChangestamp(change);
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
return CancelCallback();
}
}
}
......@@ -1025,13 +939,11 @@ CancelCallback FakeDriveService::AddNewDirectory(
return CancelCallback();
}
const char kContentType[] = "application/atom+xml;type=feed";
const base::DictionaryValue* new_entry = AddNewEntry(kContentType,
"", // content_data
parent_resource_id,
directory_title,
false, // shared_with_me
"folder");
const EntryInfo* new_entry = AddNewEntry(kDriveFolderMimeType,
"", // content_data
parent_resource_id,
directory_title,
false); // shared_with_me
if (!new_entry) {
scoped_ptr<ResourceEntry> null;
base::MessageLoop::current()->PostTask(
......@@ -1040,7 +952,8 @@ CancelCallback FakeDriveService::AddNewDirectory(
return CancelCallback();
}
scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*new_entry));
scoped_ptr<ResourceEntry> parsed_entry(
util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
......@@ -1064,7 +977,7 @@ CancelCallback FakeDriveService::InitiateUploadNewFile(
}
if (parent_resource_id != GetRootResourceId() &&
!FindEntryByResourceId(parent_resource_id)) {
!entries_.count(parent_resource_id)) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_NOT_FOUND, GURL()));
......@@ -1101,7 +1014,7 @@ CancelCallback FakeDriveService::InitiateUploadExistingFile(
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (!entry) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
......@@ -1109,9 +1022,8 @@ CancelCallback FakeDriveService::InitiateUploadExistingFile(
return CancelCallback();
}
std::string entry_etag;
entry->GetString("gd$etag", &entry_etag);
if (!etag.empty() && etag != entry_etag) {
const FileResource* file = entry->change_resource.file();
if (!etag.empty() && etag != file->etag()) {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_PRECONDITION, GURL()));
......@@ -1123,7 +1035,7 @@ CancelCallback FakeDriveService::InitiateUploadExistingFile(
UploadSession(content_type, content_length,
"", // parent_resource_id
resource_id,
entry_etag,
file->etag(),
"" /* title */);
base::MessageLoop::current()->PostTask(
......@@ -1209,45 +1121,44 @@ CancelCallback FakeDriveService::ResumeUpload(
if (session->resource_id.empty()) {
DCHECK(!session->parent_resource_id.empty());
DCHECK(!session->title.empty());
const base::DictionaryValue* new_entry = AddNewEntry(
const EntryInfo* new_entry = AddNewEntry(
session->content_type,
content_data,
session->parent_resource_id,
session->title,
false, // shared_with_me
"file");
false); // shared_with_me
if (!new_entry) {
completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
return CancelCallback();
}
completion_callback.Run(HTTP_CREATED,
ResourceEntry::CreateFrom(*new_entry));
completion_callback.Run(
HTTP_CREATED,
util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
return CancelCallback();
}
base::DictionaryValue* entry = FindEntryByResourceId(session->resource_id);
EntryInfo* entry = FindEntryByResourceId(session->resource_id);
if (!entry) {
completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>());
return CancelCallback();
}
std::string entry_etag;
entry->GetString("gd$etag", &entry_etag);
if (entry_etag.empty() || session->etag != entry_etag) {
ChangeResource* change = &entry->change_resource;
FileResource* file = change->mutable_file();
if (file->etag().empty() || session->etag != file->etag()) {
completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<ResourceEntry>());
return CancelCallback();
}
entry->SetString("docs$md5Checksum.$t", base::MD5String(content_data));
entry->Set("test$data",
base::BinaryValue::CreateWithCopiedBuffer(
content_data.data(), content_data.size()));
entry->SetString("docs$size.$t", base::Int64ToString(end_position));
AddNewChangestamp(entry);
UpdateETag(entry);
file->set_md5_checksum(base::MD5String(content_data));
entry->content_data = content_data;
file->set_file_size(end_position);
AddNewChangestamp(change);
UpdateETag(file);
completion_callback.Run(HTTP_SUCCESS, ResourceEntry::CreateFrom(*entry));
completion_callback.Run(HTTP_SUCCESS,
util::ConvertChangeResourceToResourceEntry(*change));
return CancelCallback();
}
......@@ -1368,19 +1279,11 @@ void FakeDriveService::AddNewFile(const std::string& content_type,
return;
}
// Prepare "kind" for hosted documents. This only supports Google Document.
std::string entry_kind;
if (content_type == "application/vnd.google-apps.document")
entry_kind = "document";
else
entry_kind = "file";
const base::DictionaryValue* new_entry = AddNewEntry(content_type,
content_data,
parent_resource_id,
title,
shared_with_me,
entry_kind);
const EntryInfo* new_entry = AddNewEntry(content_type,
content_data,
parent_resource_id,
title,
shared_with_me);
if (!new_entry) {
scoped_ptr<ResourceEntry> null;
base::MessageLoop::current()->PostTask(
......@@ -1390,7 +1293,7 @@ void FakeDriveService::AddNewFile(const std::string& content_type,
}
scoped_ptr<ResourceEntry> parsed_entry(
ResourceEntry::CreateFrom(*new_entry));
util::ConvertChangeResourceToResourceEntry(new_entry->change_resource));
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_CREATED, base::Passed(&parsed_entry)));
......@@ -1413,7 +1316,7 @@ void FakeDriveService::SetLastModifiedTime(
return;
}
base::DictionaryValue* entry = FindEntryByResourceId(resource_id);
EntryInfo* entry = FindEntryByResourceId(resource_id);
if (!entry) {
scoped_ptr<ResourceEntry> null;
base::MessageLoop::current()->PostTask(
......@@ -1422,62 +1325,25 @@ void FakeDriveService::SetLastModifiedTime(
return;
}
if (last_modified_time.is_null()) {
entry->Remove("updated.$t", NULL);
} else {
entry->SetString(
"updated.$t",
google_apis::util::FormatTimeAsString(last_modified_time));
}
ChangeResource* change = &entry->change_resource;
FileResource* file = change->mutable_file();
file->set_modified_date(last_modified_time);
scoped_ptr<ResourceEntry> parsed_entry(
ResourceEntry::CreateFrom(*entry));
util::ConvertChangeResourceToResourceEntry(*change));
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(callback, HTTP_SUCCESS, base::Passed(&parsed_entry)));
}
base::DictionaryValue* FakeDriveService::FindEntryByResourceId(
FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
const std::string& resource_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::ListValue* entries = NULL;
// Go through entries and return the one that matches |resource_id|.
if (resource_list_value_->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
std::string current_resource_id;
if (entries->GetDictionary(i, &entry) &&
entry->GetString("gd$resourceId.$t", &current_resource_id) &&
resource_id == current_resource_id &&
!entry->HasKey("docs$removed")) {
return entry;
}
}
}
return NULL;
}
base::DictionaryValue* FakeDriveService::FindEntryByContentUrl(
const GURL& content_url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
base::ListValue* entries = NULL;
// Go through entries and return the one that matches |content_url|.
if (resource_list_value_->GetList("entry", &entries)) {
for (size_t i = 0; i < entries->GetSize(); ++i) {
base::DictionaryValue* entry = NULL;
std::string current_content_url;
if (entries->GetDictionary(i, &entry) &&
entry->GetString("content.src", &current_content_url) &&
content_url == GURL(current_content_url)) {
return entry;
}
}
}
return NULL;
EntryInfoMap::iterator it = entries_.find(resource_id);
// Deleted entries don't have FileResource.
return it != entries_.end() && it->second->change_resource.file() ?
it->second : NULL;
}
std::string FakeDriveService::GetNewResourceId() {
......@@ -1487,125 +1353,86 @@ std::string FakeDriveService::GetNewResourceId() {
return base::StringPrintf("resource_id_%d", resource_id_count_);
}
void FakeDriveService::UpdateETag(base::DictionaryValue* entry) {
entry->SetString("gd$etag",
"etag_" + base::Int64ToString(largest_changestamp_));
void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
file->set_etag("etag_" + base::Int64ToString(largest_changestamp_));
}
void FakeDriveService::AddNewChangestamp(base::DictionaryValue* entry) {
void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) {
++largest_changestamp_;
entry->SetString("docs$changestamp.value",
base::Int64ToString(largest_changestamp_));
change->set_change_id(largest_changestamp_);
}
const base::DictionaryValue* FakeDriveService::AddNewEntry(
const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
const std::string& content_type,
const std::string& content_data,
const std::string& parent_resource_id,
const std::string& title,
bool shared_with_me,
const std::string& entry_kind) {
bool shared_with_me) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!parent_resource_id.empty() &&
parent_resource_id != GetRootResourceId() &&
!FindEntryByResourceId(parent_resource_id)) {
!entries_.count(parent_resource_id)) {
return NULL;
}
std::string resource_id = GetNewResourceId();
GURL upload_url = GURL("https://xxx/upload/" + resource_id);
scoped_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue);
scoped_ptr<EntryInfo> new_entry(new EntryInfo);
ChangeResource* new_change = &new_entry->change_resource;
FileResource* new_file = new FileResource;
new_change->set_file(make_scoped_ptr(new_file));
// Set the resource ID and the title
new_entry->SetString("gd$resourceId.$t", resource_id);
new_entry->SetString("title.$t", title);
new_entry->SetString("docs$filename", title);
new_change->set_file_id(resource_id);
new_file->set_file_id(resource_id);
new_file->set_title(title);
// Set the contents, size and MD5 for a file.
if (entry_kind == "file") {
new_entry->Set("test$data",
base::BinaryValue::CreateWithCopiedBuffer(
content_data.c_str(), content_data.size()));
new_entry->SetString("docs$size.$t",
base::Int64ToString(content_data.size()));
new_entry->SetString("docs$md5Checksum.$t",
base::MD5String(content_data));
if (content_type != kDriveFolderMimeType) {
new_entry->content_data = content_data;
new_file->set_file_size(content_data.size());
new_file->set_md5_checksum(base::MD5String(content_data));
}
// Add "category" which sets the resource type to |entry_kind|.
base::ListValue* categories = new base::ListValue;
base::DictionaryValue* category = new base::DictionaryValue;
category->SetString("scheme", "http://schemas.google.com/g/2005#kind");
category->SetString("term", "http://schemas.google.com/docs/2007#" +
entry_kind);
categories->Append(category);
new_entry->Set("category", categories);
if (shared_with_me) {
base::DictionaryValue* shared_with_me_label = new base::DictionaryValue;
shared_with_me_label->SetString("label", "shared-with-me");
shared_with_me_label->SetString("scheme",
"http://schemas.google.com/g/2005/labels");
shared_with_me_label->SetString(
"term", "http://schemas.google.com/g/2005/labels#shared");
categories->Append(shared_with_me_label);
// Set current time to mark the file as shared_with_me.
new_file->set_shared_with_me_date(base::Time::Now());
}
std::string escaped_resource_id = net::EscapePath(resource_id);
// Add "content" which sets the content URL.
base::DictionaryValue* content = new base::DictionaryValue;
content->SetString("src", "https://xxx/content/" + escaped_resource_id);
content->SetString("type", content_type);
new_entry->Set("content", content);
// Add "link" which sets the parent URL, the edit URL and the upload URL.
base::ListValue* links = new base::ListValue;
// Set download URL and mime type.
new_file->set_download_url(
GURL("https://xxx/content/" + escaped_resource_id));
new_file->set_mime_type(content_type);
base::DictionaryValue* parent_link = new base::DictionaryValue;
// Set parents.
scoped_ptr<ParentReference> parent(new ParentReference);
if (parent_resource_id.empty())
parent_link->SetString("href", GetFakeLinkUrl(GetRootResourceId()).spec());
parent->set_file_id(GetRootResourceId());
else
parent_link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec());
parent_link->SetString("rel",
"http://schemas.google.com/docs/2007#parent");
links->Append(parent_link);
base::DictionaryValue* edit_link = new base::DictionaryValue;
edit_link->SetString("href", "https://xxx/edit/" + escaped_resource_id);
edit_link->SetString("rel", "edit");
links->Append(edit_link);
base::DictionaryValue* upload_link = new base::DictionaryValue;
upload_link->SetString("href", upload_url.spec());
upload_link->SetString("rel", kUploadUrlRel);
links->Append(upload_link);
const GURL share_url = net::AppendOrReplaceQueryParameter(
parent->set_file_id(parent_resource_id);
parent->set_parent_link(GetFakeLinkUrl(parent->file_id()));
parent->set_is_root(parent->file_id() == GetRootResourceId());
ScopedVector<ParentReference> parents;
parents.push_back(parent.release());
new_file->set_parents(parents.Pass());
new_file->set_self_link(GURL("https://xxx/edit/" + escaped_resource_id));
new_entry->share_url = net::AppendOrReplaceQueryParameter(
share_url_base_, "name", title);
base::DictionaryValue* share_link = new base::DictionaryValue;
upload_link->SetString("href", share_url.spec());
upload_link->SetString("rel", kShareUrlRel);
links->Append(share_link);
new_entry->Set("link", links);
AddNewChangestamp(new_entry.get());
UpdateETag(new_entry.get());
AddNewChangestamp(new_change);
UpdateETag(new_file);
base::Time published_date =
base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
new_entry->SetString(
"published.$t",
google_apis::util::FormatTimeAsString(published_date));
// If there are no entries, prepare an empty entry to add.
if (!resource_list_value_->HasKey("entry"))
resource_list_value_->Set("entry", new base::ListValue);
base::DictionaryValue* raw_new_entry = new_entry.release();
base::ListValue* entries = NULL;
if (resource_list_value_->GetList("entry", &entries))
entries->Append(raw_new_entry);
new_file->set_created_date(published_date);
EntryInfo* raw_new_entry = new_entry.release();
entries_[resource_id] = raw_new_entry;
return raw_new_entry;
}
......@@ -1626,21 +1453,14 @@ void FakeDriveService::GetResourceListInternal(
return;
}
scoped_ptr<ResourceList> resource_list =
ResourceList::CreateFrom(*resource_list_value_);
// TODO(hashimoto): Drive API always provides largest changestamp. Remove this
// if-statement after API switch.
if (start_changestamp > 0 && start_offset == 0)
resource_list->set_largest_changestamp(largest_changestamp_);
// Filter out entries per parameters like |directory_resource_id| and
// |search_query|.
ScopedVector<ResourceEntry>* entries = resource_list->mutable_entries();
ScopedVector<ResourceEntry> entries;
int num_entries_matched = 0;
for (size_t i = 0; i < entries->size();) {
ResourceEntry* entry = (*entries)[i];
for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
++it) {
scoped_ptr<ResourceEntry> entry =
util::ConvertChangeResourceToResourceEntry(it->second->change_resource);
bool should_exclude = false;
// If |directory_resource_id| is set, exclude the entry if it's not in
......@@ -1688,16 +1508,18 @@ void FakeDriveService::GetResourceListInternal(
if (start_offset > 0 && num_entries_matched <= start_offset)
should_exclude = true;
if (should_exclude)
entries->erase(entries->begin() + i);
else
++i;
if (!should_exclude)
entries.push_back(entry.release());
}
scoped_ptr<ResourceList> resource_list(new ResourceList);
if (start_changestamp > 0 && start_offset == 0)
resource_list->set_largest_changestamp(largest_changestamp_);
// If |max_results| is set, trim the entries if the number exceeded the max
// results.
if (max_results > 0 && entries->size() > static_cast<size_t>(max_results)) {
entries->erase(entries->begin() + max_results, entries->end());
if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
entries.erase(entries.begin() + max_results, entries.end());
// Adds the next URL.
// Here, we embed information which is needed for continuing the
// GetResourceList request in the next invocation into url query
......@@ -1725,6 +1547,7 @@ void FakeDriveService::GetResourceListInternal(
link->set_href(next_url);
resource_list->mutable_links()->push_back(link);
}
resource_list->set_entries(entries.Pass());
if (load_counter)
*load_counter += 1;
......
......@@ -10,6 +10,11 @@
#include "chrome/browser/drive/drive_service_interface.h"
#include "google_apis/drive/auth_service_interface.h"
namespace google_apis {
class ChangeResource;
class FileResource;
}
namespace drive {
// This class implements a fake DriveService which acts like a real Drive
......@@ -249,36 +254,31 @@ class FakeDriveService : public DriveServiceInterface {
const google_apis::GetResourceEntryCallback& callback);
private:
struct EntryInfo;
struct UploadSession;
// Returns a pointer to the entry that matches |resource_id|, or NULL if
// not found.
base::DictionaryValue* FindEntryByResourceId(const std::string& resource_id);
// Returns a pointer to the entry that matches |content_url|, or NULL if
// not found.
base::DictionaryValue* FindEntryByContentUrl(const GURL& content_url);
EntryInfo* FindEntryByResourceId(const std::string& resource_id);
// Returns a new resource ID, which looks like "resource_id_<num>" where
// <num> is a monotonically increasing number starting from 1.
std::string GetNewResourceId();
// Increments |largest_changestamp_| and adds the new changestamp.
void AddNewChangestamp(base::DictionaryValue* entry);
void AddNewChangestamp(google_apis::ChangeResource* change);
// Update ETag of |entry| based on |largest_changestamp_|.
void UpdateETag(base::DictionaryValue* entry);
// Update ETag of |file| based on |largest_changestamp_|.
void UpdateETag(google_apis::FileResource* file);
// Adds a new entry based on the given parameters. |entry_kind| should be
// "file" or "folder". Returns a pointer to the newly added entry, or NULL
// if failed.
const base::DictionaryValue* AddNewEntry(
// Adds a new entry based on the given parameters.
// Returns a pointer to the newly added entry, or NULL if failed.
const EntryInfo* AddNewEntry(
const std::string& content_type,
const std::string& content_data,
const std::string& parent_resource_id,
const std::string& title,
bool shared_with_me,
const std::string& entry_kind);
bool shared_with_me);
// Core implementation of GetResourceList.
// This method returns the slice of the all matched entries, and its range
......@@ -297,7 +297,8 @@ class FakeDriveService : public DriveServiceInterface {
// Returns new upload session URL.
GURL GetNewUploadSessionUrl();
scoped_ptr<base::DictionaryValue> resource_list_value_;
typedef std::map<std::string, EntryInfo*> EntryInfoMap;
EntryInfoMap entries_;
scoped_ptr<base::DictionaryValue> account_metadata_value_;
scoped_ptr<base::DictionaryValue> app_info_value_;
std::map<GURL, UploadSession> upload_sessions_;
......
......@@ -557,6 +557,7 @@ class FileResource {
// Returns parent references (directories) of this file.
const ScopedVector<ParentReference>& parents() const { return parents_; }
ScopedVector<ParentReference>* mutable_parents() { return &parents_; }
// Returns the link to the file's thumbnail.
const GURL& thumbnail_link() const { return thumbnail_link_; }
......@@ -761,6 +762,7 @@ class ChangeResource {
// Returns FileResource of the file which the change refers to.
const FileResource* file() const { return file_.get(); }
FileResource* mutable_file() { return file_.get(); }
void set_change_id(int64 change_id) {
change_id_ = change_id;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment