Chromium Code Reviews| Index: chrome/browser/drive/fake_drive_service.cc |
| diff --git a/chrome/browser/drive/fake_drive_service.cc b/chrome/browser/drive/fake_drive_service.cc |
| index 32d9d2d1720bb47382ac018741308110a07e894f..730af88c590f1cfbec69ed6805d461359d7a4e3f 100644 |
| --- a/chrome/browser/drive/fake_drive_service.cc |
| +++ b/chrome/browser/drive/fake_drive_service.cc |
| @@ -6,7 +6,9 @@ |
| #include <string> |
| +#include "base/file_util.h" |
| #include "base/logging.h" |
| +#include "base/md5.h" |
| #include "base/message_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| @@ -96,28 +98,6 @@ bool EntryMatchWithQuery(const ResourceEntry& entry, |
| return true; |
| } |
| -// Gets the upload URL from the given entry. Returns an empty URL if not |
| -// found. |
| -GURL GetUploadUrl(const base::DictionaryValue& entry) { |
| - std::string upload_url; |
| - const base::ListValue* links = NULL; |
| - if (entry.GetList("link", &links) && links) { |
| - for (size_t link_index = 0; |
| - link_index < links->GetSize(); |
| - ++link_index) { |
| - const base::DictionaryValue* link = NULL; |
| - std::string rel; |
| - if (links->GetDictionary(link_index, &link) && |
| - link && link->GetString("rel", &rel) && |
| - rel == kUploadUrlRel && |
| - link->GetString("href", &upload_url)) { |
| - break; |
| - } |
| - } |
| - } |
| - return GURL(upload_url); |
| -} |
| - |
| // Returns |url| without query parameter. |
| GURL RemoveQueryParameter(const GURL& url) { |
| GURL::Replacements replacements; |
| @@ -127,9 +107,41 @@ GURL RemoveQueryParameter(const GURL& url) { |
| } // namespace |
| +struct FakeDriveService::UploadSession { |
| + std::string content_type; |
| + int64 content_length; |
| + std::string parent_resource_id; |
| + std::string resource_id; |
| + std::string etag; |
| + std::string title; |
| + |
| + int64 uploaded_size; |
| + |
| + UploadSession() |
| + : content_length(0), |
| + uploaded_size(0) {} |
| + |
| + UploadSession( |
| + std::string content_type, |
| + int64 content_length, |
| + std::string parent_resource_id, |
| + std::string resource_id, |
| + std::string etag, |
| + std::string title) |
| + : content_type(content_type), |
| + content_length(content_length), |
| + parent_resource_id(parent_resource_id), |
| + resource_id(resource_id), |
| + etag(etag), |
| + title(title), |
| + uploaded_size(0) { |
| + } |
| +}; |
| + |
| FakeDriveService::FakeDriveService() |
| : largest_changestamp_(0), |
| published_date_seq_(0), |
| + next_upload_sequence_number_(0), |
| default_max_results_(0), |
| resource_id_count_(0), |
| resource_list_load_count_(0), |
| @@ -548,7 +560,6 @@ CancelCallback FakeDriveService::DownloadFile( |
| // Write "x"s of the file size specified in the entry. |
| std::string file_size_string; |
| entry->GetString("docs$size.$t", &file_size_string); |
| - // TODO(satorux): To be correct, we should update docs$md5Checksum.$t here. |
| int64 file_size = 0; |
| if (base::StringToInt64(file_size_string, &file_size)) { |
| base::BinaryValue* content_binary_data; |
| @@ -650,7 +661,7 @@ CancelCallback FakeDriveService::CopyResource( |
| link->SetString("href", GetFakeLinkUrl(parent_resource_id).spec()); |
| links->Append(link); |
| - AddNewChangestamp(copied_entry.get()); |
| + AddNewChangestampAndETag(copied_entry.get()); |
| // Parse the new entry. |
| scoped_ptr<ResourceEntry> resource_entry = |
| @@ -701,7 +712,7 @@ CancelCallback FakeDriveService::RenameResource( |
| base::DictionaryValue* entry = FindEntryByResourceId(resource_id); |
| if (entry) { |
| entry->SetString("title.$t", new_name); |
| - AddNewChangestamp(entry); |
| + AddNewChangestampAndETag(entry); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); |
| return CancelCallback(); |
| @@ -743,7 +754,7 @@ CancelCallback FakeDriveService::TouchResource( |
| util::FormatTimeAsString(modified_date)); |
| entry->SetString("gd$lastViewed.$t", |
| util::FormatTimeAsString(last_viewed_by_me_date)); |
| - AddNewChangestamp(entry); |
| + AddNewChangestampAndETag(entry); |
| scoped_ptr<ResourceEntry> parsed_entry(ResourceEntry::CreateFrom(*entry)); |
| base::MessageLoop::current()->PostTask( |
| @@ -783,7 +794,7 @@ CancelCallback FakeDriveService::AddResourceToDirectory( |
| "href", GetFakeLinkUrl(parent_resource_id).spec()); |
| links->Append(link); |
| - AddNewChangestamp(entry); |
| + AddNewChangestampAndETag(entry); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); |
| return CancelCallback(); |
| @@ -822,7 +833,7 @@ CancelCallback FakeDriveService::RemoveResourceFromDirectory( |
| rel == "http://schemas.google.com/docs/2007#parent" && |
| GURL(href) == parent_content_url) { |
| links->Remove(i, NULL); |
| - AddNewChangestamp(entry); |
| + AddNewChangestampAndETag(entry); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); |
| return CancelCallback(); |
| @@ -892,27 +903,25 @@ CancelCallback FakeDriveService::InitiateUploadNewFile( |
| return CancelCallback(); |
| } |
| - // Content length should be zero, as we'll create an empty file first. The |
| - // content will be added in ResumeUpload(). |
| - const base::DictionaryValue* new_entry = AddNewEntry(content_type, |
| - "", // content_data |
| - parent_resource_id, |
| - title, |
| - false, // shared_with_me |
| - "file"); |
| - if (!new_entry) { |
| + if (parent_resource_id != GetRootResourceId() && |
| + !FindEntryByResourceId(parent_resource_id)) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, HTTP_NOT_FOUND, GURL())); |
| return CancelCallback(); |
| } |
| - const GURL upload_url = GetUploadUrl(*new_entry); |
| - DCHECK(upload_url.is_valid()); |
| + |
| + GURL session_url = GetNewUploadSessionUrl(); |
| + upload_sessions_[session_url] = |
| + UploadSession(content_type, content_length, |
| + parent_resource_id, |
| + "", // resource_id |
| + "", // etag |
| + title); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| - base::Bind(callback, HTTP_SUCCESS, |
| - net::AppendQueryParameter(upload_url, "mode", "newfile"))); |
| + base::Bind(callback, HTTP_SUCCESS, session_url)); |
| return CancelCallback(); |
| } |
| @@ -949,15 +958,18 @@ CancelCallback FakeDriveService::InitiateUploadExistingFile( |
| base::Bind(callback, HTTP_PRECONDITION, GURL())); |
| return CancelCallback(); |
| } |
| - entry->SetString("docs$size.$t", "0"); |
| - const GURL upload_url = GetUploadUrl(*entry); |
| - DCHECK(upload_url.is_valid()); |
| + GURL session_url = GetNewUploadSessionUrl(); |
| + upload_sessions_[session_url] = |
| + UploadSession(content_type, content_length, |
| + "", // parent_resource_id |
| + resource_id, |
| + entry_etag, |
| + "" /* title */); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| - base::Bind(callback, HTTP_SUCCESS, |
| - net::AppendQueryParameter(upload_url, "mode", "existing"))); |
| + base::Bind(callback, HTTP_SUCCESS, session_url)); |
| return CancelCallback(); |
| } |
| @@ -984,8 +996,6 @@ CancelCallback FakeDriveService::ResumeUpload( |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| - scoped_ptr<ResourceEntry> result_entry; |
| - |
| if (offline_) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| @@ -993,42 +1003,36 @@ CancelCallback FakeDriveService::ResumeUpload( |
| UploadRangeResponse(GDATA_NO_CONNECTION, |
| start_position, |
| end_position), |
| - base::Passed(&result_entry))); |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| return CancelCallback(); |
| } |
| - DictionaryValue* entry = NULL; |
| - entry = FindEntryByUploadUrl(RemoveQueryParameter(upload_url)); |
| - if (!entry) { |
| + if (!upload_sessions_.count(upload_url)) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, |
| UploadRangeResponse(HTTP_NOT_FOUND, |
| start_position, |
| end_position), |
| - base::Passed(&result_entry))); |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| return CancelCallback(); |
| } |
| + UploadSession* session = &upload_sessions_[upload_url]; |
| + |
| // Chunks are required to be sent in such a ways that they fill from the start |
| // of the not-yet-uploaded part with no gaps nor overlaps. |
| - std::string current_size_string; |
| - int64 current_size; |
| - if (!entry->GetString("docs$size.$t", ¤t_size_string) || |
| - !base::StringToInt64(current_size_string, ¤t_size) || |
| - current_size != start_position) { |
| + if (session->uploaded_size != start_position) { |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, |
| UploadRangeResponse(HTTP_BAD_REQUEST, |
| start_position, |
| end_position), |
| - base::Passed(&result_entry))); |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| return CancelCallback(); |
| } |
| - entry->SetString("docs$size.$t", base::Int64ToString(end_position)); |
| - |
| if (!progress_callback.is_null()) { |
| // In the real GDataWapi/Drive DriveService, progress is reported in |
| // nondeterministic timing. In this fake implementation, we choose to call |
| @@ -1044,35 +1048,101 @@ CancelCallback FakeDriveService::ResumeUpload( |
| } |
| if (content_length != end_position) { |
| + session->uploaded_size = end_position; |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, |
| UploadRangeResponse(HTTP_RESUME_INCOMPLETE, |
| start_position, |
| end_position), |
| - base::Passed(&result_entry))); |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| return CancelCallback(); |
| } |
| - AddNewChangestamp(entry); |
| - result_entry = ResourceEntry::CreateFrom(*entry).Pass(); |
| + std::string content_data; |
| + if (!file_util::ReadFileToString(local_file_path, &content_data)) { |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(callback, |
| + UploadRangeResponse(GDATA_FILE_ERROR, |
| + start_position, |
| + end_position), |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| + return CancelCallback(); |
| + } |
| + session->uploaded_size = end_position; |
| + |
| + // |resource_id| is empty if the upload is for new file. |
| + if (session->resource_id.empty()) { |
| + DCHECK(!session->parent_resource_id.empty()); |
| + DCHECK(!session->title.empty()); |
| + const DictionaryValue* new_entry = AddNewEntry( |
| + session->content_type, |
| + content_data, |
| + session->parent_resource_id, |
| + session->title, |
| + false, // shared_with_me |
| + "file"); |
| + if (!new_entry) { |
| + base::MessageLoop::current()->PostTask( |
|
hashimoto
2013/06/21 05:41:24
nit: The only difference between PostTask here and
tzik
2013/06/21 07:54:02
Did you mean a reduction like this?
https://codere
hashimoto
2013/06/21 08:05:32
Wow, I was only expecting the calls for HTTP_NOT_F
|
| + FROM_HERE, |
| + base::Bind(callback, |
| + UploadRangeResponse(HTTP_NOT_FOUND, |
| + start_position, |
| + end_position), |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| + return CancelCallback(); |
| + } |
| - std::string upload_mode; |
| - bool upload_mode_found = |
| - net::GetValueForKeyInQuery(upload_url, "mode", &upload_mode); |
| - DCHECK(upload_mode_found && |
| - (upload_mode == "newfile" || upload_mode == "existing")); |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(callback, |
| + UploadRangeResponse(HTTP_CREATED, |
| + start_position, |
| + end_position), |
| + base::Passed(ResourceEntry::CreateFrom(*new_entry)))); |
| + return CancelCallback(); |
| + } |
| - GDataErrorCode return_code = |
| - upload_mode == "newfile" ? HTTP_CREATED : HTTP_SUCCESS; |
| + DictionaryValue* entry = FindEntryByResourceId(session->resource_id); |
| + if (!entry) { |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(callback, |
| + UploadRangeResponse(HTTP_NOT_FOUND, |
| + start_position, |
| + end_position), |
| + base::Passed(scoped_ptr<ResourceEntry>()))); |
| + return CancelCallback(); |
| + } |
| + |
| + std::string entry_etag; |
| + entry->GetString("gd$etag", &entry_etag); |
| + if (entry_etag.empty() || session->etag != entry_etag) { |
| + base::MessageLoop::current()->PostTask( |
| + FROM_HERE, |
| + base::Bind(callback, |
| + UploadRangeResponse(HTTP_PRECONDITION, |
| + start_position, |
| + end_position), |
| + base::Passed(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)); |
| + AddNewChangestampAndETag(entry); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, |
| - UploadRangeResponse(return_code, |
| + UploadRangeResponse(HTTP_SUCCESS, |
| start_position, |
| end_position), |
| - base::Passed(&result_entry))); |
| + base::Passed(ResourceEntry::CreateFrom(*entry)))); |
| return CancelCallback(); |
| } |
| @@ -1214,40 +1284,6 @@ base::DictionaryValue* FakeDriveService::FindEntryByContentUrl( |
| return NULL; |
| } |
| -base::DictionaryValue* FakeDriveService::FindEntryByUploadUrl( |
| - const GURL& upload_url) { |
| - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| - |
| - base::ListValue* entries = NULL; |
| - // Go through entries and return the one that matches |upload_url|. |
| - if (resource_list_value_->GetList("entry", &entries)) { |
| - for (size_t i = 0; i < entries->GetSize(); ++i) { |
| - base::DictionaryValue* entry = NULL; |
| - base::ListValue* links = NULL; |
| - if (entries->GetDictionary(i, &entry) && |
| - entry->GetList("link", &links) && |
| - links) { |
| - for (size_t link_index = 0; |
| - link_index < links->GetSize(); |
| - ++link_index) { |
| - base::DictionaryValue* link = NULL; |
| - std::string rel; |
| - std::string found_upload_url; |
| - if (links->GetDictionary(link_index, &link) && |
| - link && link->GetString("rel", &rel) && |
| - rel == kUploadUrlRel && |
| - link->GetString("href", &found_upload_url) && |
| - GURL(found_upload_url) == upload_url) { |
| - return entry; |
| - } |
| - } |
| - } |
| - } |
| - } |
| - |
| - return NULL; |
| -} |
| - |
| std::string FakeDriveService::GetNewResourceId() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| @@ -1255,10 +1291,12 @@ std::string FakeDriveService::GetNewResourceId() { |
| return base::StringPrintf("resource_id_%d", resource_id_count_); |
| } |
| -void FakeDriveService::AddNewChangestamp(base::DictionaryValue* entry) { |
| +void FakeDriveService::AddNewChangestampAndETag(base::DictionaryValue* entry) { |
| ++largest_changestamp_; |
| entry->SetString("docs$changestamp.value", |
| base::Int64ToString(largest_changestamp_)); |
| + entry->SetString("gd$etag", |
| + "etag_" + base::Int64ToString(largest_changestamp_)); |
| } |
| const base::DictionaryValue* FakeDriveService::AddNewEntry( |
| @@ -1290,9 +1328,8 @@ const base::DictionaryValue* FakeDriveService::AddNewEntry( |
| content_data.c_str(), content_data.size())); |
| new_entry->SetString("docs$size.$t", |
| base::Int64ToString(content_data.size())); |
| - // TODO(satorux): Set the correct MD5 here. |
| new_entry->SetString("docs$md5Checksum.$t", |
| - "3b4385ebefec6e743574c76bbd0575de"); |
| + base::MD5String(content_data)); |
| } |
| // Add "category" which sets the resource type to |entry_kind|. |
| @@ -1341,7 +1378,7 @@ const base::DictionaryValue* FakeDriveService::AddNewEntry( |
| links->Append(upload_link); |
| new_entry->Set("link", links); |
| - AddNewChangestamp(new_entry.get()); |
| + AddNewChangestampAndETag(new_entry.get()); |
| base::Time published_date = |
| base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_); |
| @@ -1477,4 +1514,9 @@ void FakeDriveService::GetResourceListInternal( |
| base::Passed(&resource_list))); |
| } |
| +GURL FakeDriveService::GetNewUploadSessionUrl() { |
| + return GURL("https://upload_session_url/" + |
| + base::Int64ToString(next_upload_sequence_number_++)); |
| +} |
| + |
| } // namespace drive |