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 90fd132a9497b852e872eedd12ae2ec8e8976e9b..3df7ba8abe4bd364a99d1af92c56a3079a7f0d2f 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; |
@@ -125,11 +105,57 @@ GURL RemoveQueryParameter(const GURL& url) { |
return url.ReplaceComponents(replacements); |
} |
+void ScheduleUploadRangeCallback(const UploadRangeCallback& callback, |
+ int64 start_position, |
+ int64 end_position, |
+ GDataErrorCode error, |
+ scoped_ptr<ResourceEntry> entry) { |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(callback, |
+ UploadRangeResponse(error, |
+ start_position, |
+ end_position), |
+ base::Passed(&entry))); |
+} |
+ |
} // 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), |
@@ -547,7 +573,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; |
@@ -649,7 +674,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 = |
@@ -700,7 +725,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(); |
@@ -742,7 +767,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( |
@@ -782,7 +807,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(); |
@@ -821,7 +846,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(); |
@@ -890,27 +915,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(); |
} |
@@ -946,15 +969,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(); |
} |
@@ -979,51 +1005,29 @@ CancelCallback FakeDriveService::ResumeUpload( |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DCHECK(!callback.is_null()); |
- scoped_ptr<ResourceEntry> result_entry; |
+ GetResourceEntryCallback completion_callback |
+ = base::Bind(&ScheduleUploadRangeCallback, |
+ callback, start_position, end_position); |
if (offline_) { |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, |
- base::Bind(callback, |
- UploadRangeResponse(GDATA_NO_CONNECTION, |
- start_position, |
- end_position), |
- base::Passed(&result_entry))); |
+ completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<ResourceEntry>()); |
return CancelCallback(); |
} |
- DictionaryValue* entry = NULL; |
- entry = FindEntryByUploadUrl(RemoveQueryParameter(upload_url)); |
- if (!entry) { |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, |
- base::Bind(callback, |
- UploadRangeResponse(HTTP_NOT_FOUND, |
- start_position, |
- end_position), |
- base::Passed(&result_entry))); |
+ if (!upload_sessions_.count(upload_url)) { |
+ completion_callback.Run(HTTP_NOT_FOUND, 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) { |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, |
- base::Bind(callback, |
- UploadRangeResponse(HTTP_BAD_REQUEST, |
- start_position, |
- end_position), |
- base::Passed(&result_entry))); |
+ if (session->uploaded_size != start_position) { |
+ completion_callback.Run(HTTP_BAD_REQUEST, 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 |
@@ -1039,35 +1043,62 @@ CancelCallback FakeDriveService::ResumeUpload( |
} |
if (content_length != end_position) { |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, |
- base::Bind(callback, |
- UploadRangeResponse(HTTP_RESUME_INCOMPLETE, |
- start_position, |
- end_position), |
- base::Passed(&result_entry))); |
+ session->uploaded_size = end_position; |
+ completion_callback.Run(HTTP_RESUME_INCOMPLETE, |
+ 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)) { |
+ session->uploaded_size = end_position; |
+ completion_callback.Run(GDATA_FILE_ERROR, 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) { |
+ completion_callback.Run(HTTP_NOT_FOUND, 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")); |
+ completion_callback.Run(HTTP_CREATED, |
+ ResourceEntry::CreateFrom(*new_entry)); |
+ return CancelCallback(); |
+ } |
- GDataErrorCode return_code = |
- upload_mode == "newfile" ? HTTP_CREATED : HTTP_SUCCESS; |
+ DictionaryValue* entry = FindEntryByResourceId(session->resource_id); |
+ if (!entry) { |
+ completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<ResourceEntry>()); |
+ return CancelCallback(); |
+ } |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, |
- base::Bind(callback, |
- UploadRangeResponse(return_code, |
- start_position, |
- end_position), |
- base::Passed(&result_entry))); |
+ std::string entry_etag; |
+ entry->GetString("gd$etag", &entry_etag); |
+ if (entry_etag.empty() || session->etag != entry_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)); |
+ AddNewChangestampAndETag(entry); |
+ |
+ completion_callback.Run(HTTP_SUCCESS, ResourceEntry::CreateFrom(*entry)); |
return CancelCallback(); |
} |
@@ -1209,40 +1240,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)); |
@@ -1250,10 +1247,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( |
@@ -1285,9 +1284,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|. |
@@ -1336,7 +1334,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_); |
@@ -1472,4 +1470,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 |