1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/drive/fake_drive_service.h"
6
7 #include <string>
8
9 #include "base/files/file_util.h"
10 #include "base/json/json_string_value_serializer.h"
11 #include "base/logging.h"
12 #include "base/md5.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_tokenizer.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/drive/drive_api_util.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "google_apis/drive/drive_api_parser.h"
24 #include "google_apis/drive/test_util.h"
25 #include "net/base/escape.h"
26 #include "net/base/url_util.h"
27
28 using content::BrowserThread;
29 using google_apis::AboutResource;
30 using google_apis::AboutResourceCallback;
31 using google_apis::AppList;
32 using google_apis::AppListCallback;
33 using google_apis::AuthStatusCallback;
34 using google_apis::AuthorizeAppCallback;
35 using google_apis::CancelCallback;
36 using google_apis::ChangeList;
37 using google_apis::ChangeListCallback;
38 using google_apis::ChangeResource;
39 using google_apis::DownloadActionCallback;
40 using google_apis::EntryActionCallback;
41 using google_apis::FileList;
42 using google_apis::FileListCallback;
43 using google_apis::FileResource;
44 using google_apis::FileResourceCallback;
45 using google_apis::GDATA_FILE_ERROR;
46 using google_apis::GDATA_NO_CONNECTION;
47 using google_apis::GDATA_OTHER_ERROR;
48 using google_apis::GDataErrorCode;
49 using google_apis::GetContentCallback;
50 using google_apis::GetShareUrlCallback;
51 using google_apis::HTTP_BAD_REQUEST;
52 using google_apis::HTTP_CREATED;
53 using google_apis::HTTP_FORBIDDEN;
54 using google_apis::HTTP_NOT_FOUND;
55 using google_apis::HTTP_NO_CONTENT;
56 using google_apis::HTTP_PRECONDITION;
57 using google_apis::HTTP_RESUME_INCOMPLETE;
58 using google_apis::HTTP_SUCCESS;
59 using google_apis::InitiateUploadCallback;
60 using google_apis::ParentReference;
61 using google_apis::ProgressCallback;
62 using google_apis::UploadRangeResponse;
63 using google_apis::drive::UploadRangeCallback;
64 namespace test_util = google_apis::test_util;
65
66 namespace drive {
67 namespace {
68
69 // Returns true if the entry matches with the search query.
70 // Supports queries consist of following format.
71 // - Phrases quoted by double/single quotes
72 // - AND search for multiple words/phrases segmented by space
73 // - Limited attribute search. Only "title:" is supported.
EntryMatchWithQuery(const ChangeResource & entry,const std::string & query)74 bool EntryMatchWithQuery(const ChangeResource& entry,
75 const std::string& query) {
76 base::StringTokenizer tokenizer(query, " ");
77 tokenizer.set_quote_chars("\"'");
78 while (tokenizer.GetNext()) {
79 std::string key, value;
80 const std::string& token = tokenizer.token();
81 if (token.find(':') == std::string::npos) {
82 base::TrimString(token, "\"'", &value);
83 } else {
84 base::StringTokenizer key_value(token, ":");
85 key_value.set_quote_chars("\"'");
86 if (!key_value.GetNext())
87 return false;
88 key = key_value.token();
89 if (!key_value.GetNext())
90 return false;
91 base::TrimString(key_value.token(), "\"'", &value);
92 }
93
94 // TODO(peria): Deal with other attributes than title.
95 if (!key.empty() && key != "title")
96 return false;
97 // Search query in the title.
98 if (!entry.file() ||
99 entry.file()->title().find(value) == std::string::npos)
100 return false;
101 }
102 return true;
103 }
104
ScheduleUploadRangeCallback(const UploadRangeCallback & callback,int64 start_position,int64 end_position,GDataErrorCode error,scoped_ptr<FileResource> entry)105 void ScheduleUploadRangeCallback(const UploadRangeCallback& callback,
106 int64 start_position,
107 int64 end_position,
108 GDataErrorCode error,
109 scoped_ptr<FileResource> entry) {
110 base::MessageLoop::current()->PostTask(
111 FROM_HERE,
112 base::Bind(callback,
113 UploadRangeResponse(error,
114 start_position,
115 end_position),
116 base::Passed(&entry)));
117 }
118
FileListCallbackAdapter(const FileListCallback & callback,GDataErrorCode error,scoped_ptr<ChangeList> change_list)119 void FileListCallbackAdapter(const FileListCallback& callback,
120 GDataErrorCode error,
121 scoped_ptr<ChangeList> change_list) {
122 scoped_ptr<FileList> file_list;
123 if (!change_list) {
124 callback.Run(error, file_list.Pass());
125 return;
126 }
127
128 file_list.reset(new FileList);
129 file_list->set_next_link(change_list->next_link());
130 for (size_t i = 0; i < change_list->items().size(); ++i) {
131 const ChangeResource& entry = *change_list->items()[i];
132 if (entry.file())
133 file_list->mutable_items()->push_back(new FileResource(*entry.file()));
134 }
135 callback.Run(error, file_list.Pass());
136 }
137
UserHasWriteAccess(google_apis::drive::PermissionRole user_permission)138 bool UserHasWriteAccess(google_apis::drive::PermissionRole user_permission) {
139 switch (user_permission) {
140 case google_apis::drive::PERMISSION_ROLE_OWNER:
141 case google_apis::drive::PERMISSION_ROLE_WRITER:
142 return true;
143 case google_apis::drive::PERMISSION_ROLE_READER:
144 case google_apis::drive::PERMISSION_ROLE_COMMENTER:
145 break;
146 }
147 return false;
148 }
149
150 } // namespace
151
152 struct FakeDriveService::EntryInfo {
EntryInfodrive::FakeDriveService::EntryInfo153 EntryInfo() : user_permission(google_apis::drive::PERMISSION_ROLE_OWNER) {}
154
155 google_apis::ChangeResource change_resource;
156 GURL share_url;
157 std::string content_data;
158
159 // Behaves in the same way as "userPermission" described in
160 // https://developers.google.com/drive/v2/reference/files
161 google_apis::drive::PermissionRole user_permission;
162 };
163
164 struct FakeDriveService::UploadSession {
165 std::string content_type;
166 int64 content_length;
167 std::string parent_resource_id;
168 std::string resource_id;
169 std::string etag;
170 std::string title;
171
172 int64 uploaded_size;
173
UploadSessiondrive::FakeDriveService::UploadSession174 UploadSession()
175 : content_length(0),
176 uploaded_size(0) {}
177
UploadSessiondrive::FakeDriveService::UploadSession178 UploadSession(
179 std::string content_type,
180 int64 content_length,
181 std::string parent_resource_id,
182 std::string resource_id,
183 std::string etag,
184 std::string title)
185 : content_type(content_type),
186 content_length(content_length),
187 parent_resource_id(parent_resource_id),
188 resource_id(resource_id),
189 etag(etag),
190 title(title),
191 uploaded_size(0) {
192 }
193 };
194
FakeDriveService()195 FakeDriveService::FakeDriveService()
196 : about_resource_(new AboutResource),
197 published_date_seq_(0),
198 next_upload_sequence_number_(0),
199 default_max_results_(0),
200 resource_id_count_(0),
201 file_list_load_count_(0),
202 change_list_load_count_(0),
203 directory_load_count_(0),
204 about_resource_load_count_(0),
205 app_list_load_count_(0),
206 blocked_file_list_load_count_(0),
207 offline_(false),
208 never_return_all_file_list_(false),
209 share_url_base_("https://share_url/"),
210 weak_ptr_factory_(this) {
211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212
213 about_resource_->set_largest_change_id(654321);
214 about_resource_->set_quota_bytes_total(9876543210);
215 about_resource_->set_quota_bytes_used(6789012345);
216 about_resource_->set_root_folder_id(GetRootResourceId());
217 }
218
~FakeDriveService()219 FakeDriveService::~FakeDriveService() {
220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
221 STLDeleteValues(&entries_);
222 }
223
LoadAppListForDriveApi(const std::string & relative_path)224 bool FakeDriveService::LoadAppListForDriveApi(
225 const std::string& relative_path) {
226 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227
228 // Load JSON data, which must be a dictionary.
229 scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path);
230 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
231 app_info_value_.reset(
232 static_cast<base::DictionaryValue*>(value.release()));
233 return app_info_value_;
234 }
235
AddApp(const std::string & app_id,const std::string & app_name,const std::string & product_id,const std::string & create_url)236 void FakeDriveService::AddApp(const std::string& app_id,
237 const std::string& app_name,
238 const std::string& product_id,
239 const std::string& create_url) {
240 if (app_json_template_.empty()) {
241 base::FilePath path =
242 test_util::GetTestFilePath("drive/applist_app_template.json");
243 CHECK(base::ReadFileToString(path, &app_json_template_));
244 }
245
246 std::string app_json = app_json_template_;
247 ReplaceSubstringsAfterOffset(&app_json, 0, "$AppId", app_id);
248 ReplaceSubstringsAfterOffset(&app_json, 0, "$AppName", app_name);
249 ReplaceSubstringsAfterOffset(&app_json, 0, "$ProductId", product_id);
250 ReplaceSubstringsAfterOffset(&app_json, 0, "$CreateUrl", create_url);
251
252 JSONStringValueSerializer json(app_json);
253 std::string error_message;
254 scoped_ptr<base::Value> value(json.Deserialize(NULL, &error_message));
255 CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType());
256
257 base::ListValue* item_list;
258 CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
259 item_list->Append(value.release());
260 }
261
RemoveAppByProductId(const std::string & product_id)262 void FakeDriveService::RemoveAppByProductId(const std::string& product_id) {
263 base::ListValue* item_list;
264 CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
265 for (size_t i = 0; i < item_list->GetSize(); ++i) {
266 base::DictionaryValue* item;
267 CHECK(item_list->GetDictionary(i, &item));
268 const char kKeyProductId[] = "productId";
269 std::string item_product_id;
270 if (item->GetStringWithoutPathExpansion(kKeyProductId, &item_product_id) &&
271 product_id == item_product_id) {
272 item_list->Remove(i, NULL);
273 return;
274 }
275 }
276 }
277
HasApp(const std::string & app_id) const278 bool FakeDriveService::HasApp(const std::string& app_id) const {
279 base::ListValue* item_list;
280 CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list));
281 for (size_t i = 0; i < item_list->GetSize(); ++i) {
282 base::DictionaryValue* item;
283 CHECK(item_list->GetDictionary(i, &item));
284 const char kKeyId[] = "id";
285 std::string item_id;
286 if (item->GetStringWithoutPathExpansion(kKeyId, &item_id) &&
287 item_id == app_id) {
288 return true;
289 }
290 }
291
292 return false;
293 }
294
SetQuotaValue(int64 used,int64 total)295 void FakeDriveService::SetQuotaValue(int64 used, int64 total) {
296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
297
298 about_resource_->set_quota_bytes_used(used);
299 about_resource_->set_quota_bytes_total(total);
300 }
301
GetFakeLinkUrl(const std::string & resource_id)302 GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
303 return GURL("https://fake_server/" + net::EscapePath(resource_id));
304 }
305
Initialize(const std::string & account_id)306 void FakeDriveService::Initialize(const std::string& account_id) {
307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
308 }
309
AddObserver(DriveServiceObserver * observer)310 void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312 }
313
RemoveObserver(DriveServiceObserver * observer)314 void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
315 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
316 }
317
CanSendRequest() const318 bool FakeDriveService::CanSendRequest() const {
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320 return true;
321 }
322
HasAccessToken() const323 bool FakeDriveService::HasAccessToken() const {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
325 return true;
326 }
327
RequestAccessToken(const AuthStatusCallback & callback)328 void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) {
329 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
330 DCHECK(!callback.is_null());
331 callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token");
332 }
333
HasRefreshToken() const334 bool FakeDriveService::HasRefreshToken() const {
335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
336 return true;
337 }
338
ClearAccessToken()339 void FakeDriveService::ClearAccessToken() {
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
341 }
342
ClearRefreshToken()343 void FakeDriveService::ClearRefreshToken() {
344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
345 }
346
GetRootResourceId() const347 std::string FakeDriveService::GetRootResourceId() const {
348 return "fake_root";
349 }
350
GetAllFileList(const FileListCallback & callback)351 CancelCallback FakeDriveService::GetAllFileList(
352 const FileListCallback& callback) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
354 DCHECK(!callback.is_null());
355
356 if (never_return_all_file_list_) {
357 ++blocked_file_list_load_count_;
358 return CancelCallback();
359 }
360
361 GetChangeListInternal(0, // start changestamp
362 std::string(), // empty search query
363 std::string(), // no directory resource id,
364 0, // start offset
365 default_max_results_,
366 &file_list_load_count_,
367 base::Bind(&FileListCallbackAdapter, callback));
368 return CancelCallback();
369 }
370
GetFileListInDirectory(const std::string & directory_resource_id,const FileListCallback & callback)371 CancelCallback FakeDriveService::GetFileListInDirectory(
372 const std::string& directory_resource_id,
373 const FileListCallback& callback) {
374 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
375 DCHECK(!directory_resource_id.empty());
376 DCHECK(!callback.is_null());
377
378 GetChangeListInternal(0, // start changestamp
379 std::string(), // empty search query
380 directory_resource_id,
381 0, // start offset
382 default_max_results_,
383 &directory_load_count_,
384 base::Bind(&FileListCallbackAdapter, callback));
385 return CancelCallback();
386 }
387
Search(const std::string & search_query,const FileListCallback & callback)388 CancelCallback FakeDriveService::Search(
389 const std::string& search_query,
390 const FileListCallback& callback) {
391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
392 DCHECK(!search_query.empty());
393 DCHECK(!callback.is_null());
394
395 GetChangeListInternal(0, // start changestamp
396 search_query,
397 std::string(), // no directory resource id,
398 0, // start offset
399 default_max_results_,
400 NULL,
401 base::Bind(&FileListCallbackAdapter, callback));
402 return CancelCallback();
403 }
404
SearchByTitle(const std::string & title,const std::string & directory_resource_id,const FileListCallback & callback)405 CancelCallback FakeDriveService::SearchByTitle(
406 const std::string& title,
407 const std::string& directory_resource_id,
408 const FileListCallback& callback) {
409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
410 DCHECK(!title.empty());
411 DCHECK(!callback.is_null());
412
413 // Note: the search implementation here doesn't support quotation unescape,
414 // so don't escape here.
415 GetChangeListInternal(0, // start changestamp
416 base::StringPrintf("title:'%s'", title.c_str()),
417 directory_resource_id,
418 0, // start offset
419 default_max_results_,
420 NULL,
421 base::Bind(&FileListCallbackAdapter, callback));
422 return CancelCallback();
423 }
424
GetChangeList(int64 start_changestamp,const ChangeListCallback & callback)425 CancelCallback FakeDriveService::GetChangeList(
426 int64 start_changestamp,
427 const ChangeListCallback& callback) {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
429 DCHECK(!callback.is_null());
430
431 GetChangeListInternal(start_changestamp,
432 std::string(), // empty search query
433 std::string(), // no directory resource id,
434 0, // start offset
435 default_max_results_,
436 &change_list_load_count_,
437 callback);
438 return CancelCallback();
439 }
440
GetRemainingChangeList(const GURL & next_link,const ChangeListCallback & callback)441 CancelCallback FakeDriveService::GetRemainingChangeList(
442 const GURL& next_link,
443 const ChangeListCallback& callback) {
444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
445 DCHECK(!next_link.is_empty());
446 DCHECK(!callback.is_null());
447
448 // "changestamp", "q", "parent" and "start-offset" are parameters to
449 // implement "paging" of the result on FakeDriveService.
450 // The URL should be the one filled in GetChangeListInternal of the
451 // previous method invocation, so it should start with "http://localhost/?".
452 // See also GetChangeListInternal.
453 DCHECK_EQ(next_link.host(), "localhost");
454 DCHECK_EQ(next_link.path(), "/");
455
456 int64 start_changestamp = 0;
457 std::string search_query;
458 std::string directory_resource_id;
459 int start_offset = 0;
460 int max_results = default_max_results_;
461 base::StringPairs parameters;
462 if (base::SplitStringIntoKeyValuePairs(
463 next_link.query(), '=', '&', ¶meters)) {
464 for (size_t i = 0; i < parameters.size(); ++i) {
465 if (parameters[i].first == "changestamp") {
466 base::StringToInt64(parameters[i].second, &start_changestamp);
467 } else if (parameters[i].first == "q") {
468 search_query =
469 net::UnescapeURLComponent(parameters[i].second,
470 net::UnescapeRule::URL_SPECIAL_CHARS);
471 } else if (parameters[i].first == "parent") {
472 directory_resource_id =
473 net::UnescapeURLComponent(parameters[i].second,
474 net::UnescapeRule::URL_SPECIAL_CHARS);
475 } else if (parameters[i].first == "start-offset") {
476 base::StringToInt(parameters[i].second, &start_offset);
477 } else if (parameters[i].first == "max-results") {
478 base::StringToInt(parameters[i].second, &max_results);
479 }
480 }
481 }
482
483 GetChangeListInternal(start_changestamp, search_query, directory_resource_id,
484 start_offset, max_results, NULL, callback);
485 return CancelCallback();
486 }
487
GetRemainingFileList(const GURL & next_link,const FileListCallback & callback)488 CancelCallback FakeDriveService::GetRemainingFileList(
489 const GURL& next_link,
490 const FileListCallback& callback) {
491 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
492 DCHECK(!next_link.is_empty());
493 DCHECK(!callback.is_null());
494
495 return GetRemainingChangeList(
496 next_link, base::Bind(&FileListCallbackAdapter, callback));
497 }
498
GetFileResource(const std::string & resource_id,const FileResourceCallback & callback)499 CancelCallback FakeDriveService::GetFileResource(
500 const std::string& resource_id,
501 const FileResourceCallback& callback) {
502 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
503 DCHECK(!callback.is_null());
504
505 if (offline_) {
506 base::MessageLoop::current()->PostTask(
507 FROM_HERE,
508 base::Bind(callback,
509 GDATA_NO_CONNECTION,
510 base::Passed(scoped_ptr<FileResource>())));
511 return CancelCallback();
512 }
513
514 EntryInfo* entry = FindEntryByResourceId(resource_id);
515 if (entry && entry->change_resource.file()) {
516 base::MessageLoop::current()->PostTask(
517 FROM_HERE,
518 base::Bind(callback, HTTP_SUCCESS, base::Passed(make_scoped_ptr(
519 new FileResource(*entry->change_resource.file())))));
520 return CancelCallback();
521 }
522
523 base::MessageLoop::current()->PostTask(
524 FROM_HERE,
525 base::Bind(callback, HTTP_NOT_FOUND,
526 base::Passed(scoped_ptr<FileResource>())));
527 return CancelCallback();
528 }
529
GetShareUrl(const std::string & resource_id,const GURL &,const GetShareUrlCallback & callback)530 CancelCallback FakeDriveService::GetShareUrl(
531 const std::string& resource_id,
532 const GURL& /* embed_origin */,
533 const GetShareUrlCallback& callback) {
534 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
535 DCHECK(!callback.is_null());
536
537 if (offline_) {
538 base::MessageLoop::current()->PostTask(
539 FROM_HERE,
540 base::Bind(callback,
541 GDATA_NO_CONNECTION,
542 GURL()));
543 return CancelCallback();
544 }
545
546 EntryInfo* entry = FindEntryByResourceId(resource_id);
547 if (entry) {
548 base::MessageLoop::current()->PostTask(
549 FROM_HERE,
550 base::Bind(callback, HTTP_SUCCESS, entry->share_url));
551 return CancelCallback();
552 }
553
554 base::MessageLoop::current()->PostTask(
555 FROM_HERE,
556 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
557 return CancelCallback();
558 }
559
GetAboutResource(const AboutResourceCallback & callback)560 CancelCallback FakeDriveService::GetAboutResource(
561 const AboutResourceCallback& callback) {
562 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
563 DCHECK(!callback.is_null());
564
565 if (offline_) {
566 scoped_ptr<AboutResource> null;
567 base::MessageLoop::current()->PostTask(
568 FROM_HERE,
569 base::Bind(callback,
570 GDATA_NO_CONNECTION, base::Passed(&null)));
571 return CancelCallback();
572 }
573
574 ++about_resource_load_count_;
575 scoped_ptr<AboutResource> about_resource(new AboutResource(*about_resource_));
576 base::MessageLoop::current()->PostTask(
577 FROM_HERE,
578 base::Bind(callback,
579 HTTP_SUCCESS, base::Passed(&about_resource)));
580 return CancelCallback();
581 }
582
GetAppList(const AppListCallback & callback)583 CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) {
584 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
585 DCHECK(!callback.is_null());
586 DCHECK(app_info_value_);
587
588 if (offline_) {
589 scoped_ptr<AppList> null;
590 base::MessageLoop::current()->PostTask(
591 FROM_HERE,
592 base::Bind(callback,
593 GDATA_NO_CONNECTION,
594 base::Passed(&null)));
595 return CancelCallback();
596 }
597
598 ++app_list_load_count_;
599 scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_));
600 base::MessageLoop::current()->PostTask(
601 FROM_HERE,
602 base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list)));
603 return CancelCallback();
604 }
605
DeleteResource(const std::string & resource_id,const std::string & etag,const EntryActionCallback & callback)606 CancelCallback FakeDriveService::DeleteResource(
607 const std::string& resource_id,
608 const std::string& etag,
609 const EntryActionCallback& callback) {
610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
611 DCHECK(!callback.is_null());
612
613 if (offline_) {
614 base::MessageLoop::current()->PostTask(
615 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
616 return CancelCallback();
617 }
618
619 EntryInfo* entry = FindEntryByResourceId(resource_id);
620 if (!entry) {
621 base::MessageLoop::current()->PostTask(
622 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
623 return CancelCallback();
624 }
625
626 ChangeResource* change = &entry->change_resource;
627 const FileResource* file = change->file();
628 if (change->is_deleted()) {
629 base::MessageLoop::current()->PostTask(
630 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
631 return CancelCallback();
632 }
633
634 if (!etag.empty() && etag != file->etag()) {
635 base::MessageLoop::current()->PostTask(
636 FROM_HERE, base::Bind(callback, HTTP_PRECONDITION));
637 return CancelCallback();
638 }
639
640 if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
641 base::MessageLoop::current()->PostTask(
642 FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN));
643 return CancelCallback();
644 }
645
646 change->set_deleted(true);
647 AddNewChangestamp(change);
648 change->set_file(scoped_ptr<FileResource>());
649 base::MessageLoop::current()->PostTask(
650 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
651 base::MessageLoop::current()->PostTask(
652 FROM_HERE,
653 base::Bind(&FakeDriveService::NotifyObservers,
654 weak_ptr_factory_.GetWeakPtr()));
655 return CancelCallback();
656 }
657
TrashResource(const std::string & resource_id,const EntryActionCallback & callback)658 CancelCallback FakeDriveService::TrashResource(
659 const std::string& resource_id,
660 const EntryActionCallback& callback) {
661 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
662 DCHECK(!callback.is_null());
663
664 if (offline_) {
665 base::MessageLoop::current()->PostTask(
666 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
667 return CancelCallback();
668 }
669
670 EntryInfo* entry = FindEntryByResourceId(resource_id);
671 if (!entry) {
672 base::MessageLoop::current()->PostTask(
673 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
674 return CancelCallback();
675 }
676
677 ChangeResource* change = &entry->change_resource;
678 FileResource* file = change->mutable_file();
679 if (change->is_deleted() || file->labels().is_trashed()) {
680 base::MessageLoop::current()->PostTask(
681 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
682 return CancelCallback();
683 }
684
685 if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) {
686 base::MessageLoop::current()->PostTask(
687 FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN));
688 return CancelCallback();
689 }
690
691 file->mutable_labels()->set_trashed(true);
692 AddNewChangestamp(change);
693 base::MessageLoop::current()->PostTask(
694 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
695 base::MessageLoop::current()->PostTask(
696 FROM_HERE,
697 base::Bind(&FakeDriveService::NotifyObservers,
698 weak_ptr_factory_.GetWeakPtr()));
699 return CancelCallback();
700 }
701
DownloadFile(const base::FilePath & local_cache_path,const std::string & resource_id,const DownloadActionCallback & download_action_callback,const GetContentCallback & get_content_callback,const ProgressCallback & progress_callback)702 CancelCallback FakeDriveService::DownloadFile(
703 const base::FilePath& local_cache_path,
704 const std::string& resource_id,
705 const DownloadActionCallback& download_action_callback,
706 const GetContentCallback& get_content_callback,
707 const ProgressCallback& progress_callback) {
708 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
709 DCHECK(!download_action_callback.is_null());
710
711 if (offline_) {
712 base::MessageLoop::current()->PostTask(
713 FROM_HERE,
714 base::Bind(download_action_callback,
715 GDATA_NO_CONNECTION,
716 base::FilePath()));
717 return CancelCallback();
718 }
719
720 EntryInfo* entry = FindEntryByResourceId(resource_id);
721 if (!entry || entry->change_resource.file()->IsHostedDocument()) {
722 base::MessageLoopProxy::current()->PostTask(
723 FROM_HERE,
724 base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath()));
725 return CancelCallback();
726 }
727
728 const FileResource* file = entry->change_resource.file();
729 const std::string& content_data = entry->content_data;
730 int64 file_size = file->file_size();
731 DCHECK_EQ(static_cast<size_t>(file_size), content_data.size());
732
733 if (!get_content_callback.is_null()) {
734 const int64 kBlockSize = 5;
735 for (int64 i = 0; i < file_size; i += kBlockSize) {
736 const int64 size = std::min(kBlockSize, file_size - i);
737 scoped_ptr<std::string> content_for_callback(
738 new std::string(content_data.substr(i, size)));
739 base::MessageLoopProxy::current()->PostTask(
740 FROM_HERE,
741 base::Bind(get_content_callback, HTTP_SUCCESS,
742 base::Passed(&content_for_callback)));
743 }
744 }
745
746 if (!test_util::WriteStringToFile(local_cache_path, content_data)) {
747 // Failed to write the content.
748 base::MessageLoopProxy::current()->PostTask(
749 FROM_HERE,
750 base::Bind(download_action_callback,
751 GDATA_FILE_ERROR, base::FilePath()));
752 return CancelCallback();
753 }
754
755 if (!progress_callback.is_null()) {
756 // See also the comment in ResumeUpload(). For testing that clients
757 // can handle the case progress_callback is called multiple times,
758 // here we invoke the callback twice.
759 base::MessageLoopProxy::current()->PostTask(
760 FROM_HERE,
761 base::Bind(progress_callback, file_size / 2, file_size));
762 base::MessageLoopProxy::current()->PostTask(
763 FROM_HERE,
764 base::Bind(progress_callback, file_size, file_size));
765 }
766 base::MessageLoopProxy::current()->PostTask(
767 FROM_HERE,
768 base::Bind(download_action_callback,
769 HTTP_SUCCESS,
770 local_cache_path));
771 return CancelCallback();
772 }
773
CopyResource(const std::string & resource_id,const std::string & in_parent_resource_id,const std::string & new_title,const base::Time & last_modified,const FileResourceCallback & callback)774 CancelCallback FakeDriveService::CopyResource(
775 const std::string& resource_id,
776 const std::string& in_parent_resource_id,
777 const std::string& new_title,
778 const base::Time& last_modified,
779 const FileResourceCallback& callback) {
780 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
781 DCHECK(!callback.is_null());
782
783 if (offline_) {
784 base::MessageLoop::current()->PostTask(
785 FROM_HERE,
786 base::Bind(callback,
787 GDATA_NO_CONNECTION,
788 base::Passed(scoped_ptr<FileResource>())));
789 return CancelCallback();
790 }
791
792 const std::string& parent_resource_id = in_parent_resource_id.empty() ?
793 GetRootResourceId() : in_parent_resource_id;
794
795 EntryInfo* entry = FindEntryByResourceId(resource_id);
796 if (!entry) {
797 base::MessageLoop::current()->PostTask(
798 FROM_HERE,
799 base::Bind(callback, HTTP_NOT_FOUND,
800 base::Passed(scoped_ptr<FileResource>())));
801 return CancelCallback();
802 }
803
804 // Make a copy and set the new resource ID and the new title.
805 scoped_ptr<EntryInfo> copied_entry(new EntryInfo);
806 copied_entry->content_data = entry->content_data;
807 copied_entry->share_url = entry->share_url;
808 copied_entry->change_resource.set_file(
809 make_scoped_ptr(new FileResource(*entry->change_resource.file())));
810
811 ChangeResource* new_change = &copied_entry->change_resource;
812 FileResource* new_file = new_change->mutable_file();
813 const std::string new_resource_id = GetNewResourceId();
814 new_change->set_file_id(new_resource_id);
815 new_file->set_file_id(new_resource_id);
816 new_file->set_title(new_title);
817
818 ParentReference parent;
819 parent.set_file_id(parent_resource_id);
820 parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
821 std::vector<ParentReference> parents;
822 parents.push_back(parent);
823 *new_file->mutable_parents() = parents;
824
825 if (!last_modified.is_null())
826 new_file->set_modified_date(last_modified);
827
828 AddNewChangestamp(new_change);
829 UpdateETag(new_file);
830
831 // Add the new entry to the map.
832 entries_[new_resource_id] = copied_entry.release();
833
834 base::MessageLoop::current()->PostTask(
835 FROM_HERE,
836 base::Bind(callback,
837 HTTP_SUCCESS,
838 base::Passed(make_scoped_ptr(new FileResource(*new_file)))));
839 base::MessageLoop::current()->PostTask(
840 FROM_HERE,
841 base::Bind(&FakeDriveService::NotifyObservers,
842 weak_ptr_factory_.GetWeakPtr()));
843 return CancelCallback();
844 }
845
UpdateResource(const std::string & resource_id,const std::string & parent_resource_id,const std::string & new_title,const base::Time & last_modified,const base::Time & last_viewed_by_me,const google_apis::FileResourceCallback & callback)846 CancelCallback FakeDriveService::UpdateResource(
847 const std::string& resource_id,
848 const std::string& parent_resource_id,
849 const std::string& new_title,
850 const base::Time& last_modified,
851 const base::Time& last_viewed_by_me,
852 const google_apis::FileResourceCallback& callback) {
853 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
854 DCHECK(!callback.is_null());
855
856 if (offline_) {
857 base::MessageLoop::current()->PostTask(
858 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION,
859 base::Passed(scoped_ptr<FileResource>())));
860 return CancelCallback();
861 }
862
863 EntryInfo* entry = FindEntryByResourceId(resource_id);
864 if (!entry) {
865 base::MessageLoop::current()->PostTask(
866 FROM_HERE,
867 base::Bind(callback, HTTP_NOT_FOUND,
868 base::Passed(scoped_ptr<FileResource>())));
869 return CancelCallback();
870 }
871
872 if (!UserHasWriteAccess(entry->user_permission)) {
873 base::MessageLoop::current()->PostTask(
874 FROM_HERE,
875 base::Bind(callback, HTTP_FORBIDDEN,
876 base::Passed(scoped_ptr<FileResource>())));
877 return CancelCallback();
878 }
879
880 ChangeResource* change = &entry->change_resource;
881 FileResource* file = change->mutable_file();
882
883 if (!new_title.empty())
884 file->set_title(new_title);
885
886 // Set parent if necessary.
887 if (!parent_resource_id.empty()) {
888 ParentReference parent;
889 parent.set_file_id(parent_resource_id);
890 parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
891
892 std::vector<ParentReference> parents;
893 parents.push_back(parent);
894 *file->mutable_parents() = parents;
895 }
896
897 if (!last_modified.is_null())
898 file->set_modified_date(last_modified);
899
900 if (!last_viewed_by_me.is_null())
901 file->set_last_viewed_by_me_date(last_viewed_by_me);
902
903 AddNewChangestamp(change);
904 UpdateETag(file);
905
906 base::MessageLoop::current()->PostTask(
907 FROM_HERE,
908 base::Bind(callback, HTTP_SUCCESS,
909 base::Passed(make_scoped_ptr(new FileResource(*file)))));
910 base::MessageLoop::current()->PostTask(
911 FROM_HERE,
912 base::Bind(&FakeDriveService::NotifyObservers,
913 weak_ptr_factory_.GetWeakPtr()));
914 return CancelCallback();
915 }
916
AddResourceToDirectory(const std::string & parent_resource_id,const std::string & resource_id,const EntryActionCallback & callback)917 CancelCallback FakeDriveService::AddResourceToDirectory(
918 const std::string& parent_resource_id,
919 const std::string& resource_id,
920 const EntryActionCallback& callback) {
921 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
922 DCHECK(!callback.is_null());
923
924 if (offline_) {
925 base::MessageLoop::current()->PostTask(
926 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
927 return CancelCallback();
928 }
929
930 EntryInfo* entry = FindEntryByResourceId(resource_id);
931 if (!entry) {
932 base::MessageLoop::current()->PostTask(
933 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
934 return CancelCallback();
935 }
936
937 ChangeResource* change = &entry->change_resource;
938 // On the real Drive server, resources do not necessary shape a tree
939 // structure. That is, each resource can have multiple parent.
940 // We mimic the behavior here; AddResourceToDirectoy just adds
941 // one more parent, not overwriting old ones.
942 ParentReference parent;
943 parent.set_file_id(parent_resource_id);
944 parent.set_parent_link(GetFakeLinkUrl(parent_resource_id));
945 change->mutable_file()->mutable_parents()->push_back(parent);
946
947 AddNewChangestamp(change);
948 base::MessageLoop::current()->PostTask(
949 FROM_HERE, base::Bind(callback, HTTP_SUCCESS));
950 base::MessageLoop::current()->PostTask(
951 FROM_HERE,
952 base::Bind(&FakeDriveService::NotifyObservers,
953 weak_ptr_factory_.GetWeakPtr()));
954 return CancelCallback();
955 }
956
RemoveResourceFromDirectory(const std::string & parent_resource_id,const std::string & resource_id,const EntryActionCallback & callback)957 CancelCallback FakeDriveService::RemoveResourceFromDirectory(
958 const std::string& parent_resource_id,
959 const std::string& resource_id,
960 const EntryActionCallback& callback) {
961 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
962 DCHECK(!callback.is_null());
963
964 if (offline_) {
965 base::MessageLoop::current()->PostTask(
966 FROM_HERE, base::Bind(callback, GDATA_NO_CONNECTION));
967 return CancelCallback();
968 }
969
970 EntryInfo* entry = FindEntryByResourceId(resource_id);
971 if (!entry) {
972 base::MessageLoop::current()->PostTask(
973 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
974 return CancelCallback();
975 }
976
977 ChangeResource* change = &entry->change_resource;
978 FileResource* file = change->mutable_file();
979 std::vector<ParentReference>* parents = file->mutable_parents();
980 for (size_t i = 0; i < parents->size(); ++i) {
981 if ((*parents)[i].file_id() == parent_resource_id) {
982 parents->erase(parents->begin() + i);
983 AddNewChangestamp(change);
984 base::MessageLoop::current()->PostTask(
985 FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT));
986 base::MessageLoop::current()->PostTask(
987 FROM_HERE,
988 base::Bind(&FakeDriveService::NotifyObservers,
989 weak_ptr_factory_.GetWeakPtr()));
990 return CancelCallback();
991 }
992 }
993
994 base::MessageLoop::current()->PostTask(
995 FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND));
996 return CancelCallback();
997 }
998
AddNewDirectory(const std::string & parent_resource_id,const std::string & directory_title,const AddNewDirectoryOptions & options,const FileResourceCallback & callback)999 CancelCallback FakeDriveService::AddNewDirectory(
1000 const std::string& parent_resource_id,
1001 const std::string& directory_title,
1002 const AddNewDirectoryOptions& options,
1003 const FileResourceCallback& callback) {
1004 return AddNewDirectoryWithResourceId(
1005 "",
1006 parent_resource_id.empty() ? GetRootResourceId() : parent_resource_id,
1007 directory_title,
1008 options,
1009 callback);
1010 }
1011
InitiateUploadNewFile(const std::string & content_type,int64 content_length,const std::string & parent_resource_id,const std::string & title,const InitiateUploadNewFileOptions & options,const InitiateUploadCallback & callback)1012 CancelCallback FakeDriveService::InitiateUploadNewFile(
1013 const std::string& content_type,
1014 int64 content_length,
1015 const std::string& parent_resource_id,
1016 const std::string& title,
1017 const InitiateUploadNewFileOptions& options,
1018 const InitiateUploadCallback& callback) {
1019 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1020 DCHECK(!callback.is_null());
1021
1022 if (offline_) {
1023 base::MessageLoop::current()->PostTask(
1024 FROM_HERE,
1025 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1026 return CancelCallback();
1027 }
1028
1029 if (parent_resource_id != GetRootResourceId() &&
1030 !entries_.count(parent_resource_id)) {
1031 base::MessageLoop::current()->PostTask(
1032 FROM_HERE,
1033 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1034 return CancelCallback();
1035 }
1036
1037 GURL session_url = GetNewUploadSessionUrl();
1038 upload_sessions_[session_url] =
1039 UploadSession(content_type, content_length,
1040 parent_resource_id,
1041 "", // resource_id
1042 "", // etag
1043 title);
1044
1045 base::MessageLoop::current()->PostTask(
1046 FROM_HERE,
1047 base::Bind(callback, HTTP_SUCCESS, session_url));
1048 return CancelCallback();
1049 }
1050
InitiateUploadExistingFile(const std::string & content_type,int64 content_length,const std::string & resource_id,const InitiateUploadExistingFileOptions & options,const InitiateUploadCallback & callback)1051 CancelCallback FakeDriveService::InitiateUploadExistingFile(
1052 const std::string& content_type,
1053 int64 content_length,
1054 const std::string& resource_id,
1055 const InitiateUploadExistingFileOptions& options,
1056 const InitiateUploadCallback& callback) {
1057 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1058 DCHECK(!callback.is_null());
1059
1060 if (offline_) {
1061 base::MessageLoop::current()->PostTask(
1062 FROM_HERE,
1063 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
1064 return CancelCallback();
1065 }
1066
1067 EntryInfo* entry = FindEntryByResourceId(resource_id);
1068 if (!entry) {
1069 base::MessageLoop::current()->PostTask(
1070 FROM_HERE,
1071 base::Bind(callback, HTTP_NOT_FOUND, GURL()));
1072 return CancelCallback();
1073 }
1074
1075 if (!UserHasWriteAccess(entry->user_permission)) {
1076 base::MessageLoop::current()->PostTask(
1077 FROM_HERE,
1078 base::Bind(callback, HTTP_FORBIDDEN, GURL()));
1079 return CancelCallback();
1080 }
1081
1082 FileResource* file = entry->change_resource.mutable_file();
1083 if (!options.etag.empty() && options.etag != file->etag()) {
1084 base::MessageLoop::current()->PostTask(
1085 FROM_HERE,
1086 base::Bind(callback, HTTP_PRECONDITION, GURL()));
1087 return CancelCallback();
1088 }
1089 // TODO(hashimoto): Update |file|'s metadata with |options|.
1090
1091 GURL session_url = GetNewUploadSessionUrl();
1092 upload_sessions_[session_url] =
1093 UploadSession(content_type, content_length,
1094 "", // parent_resource_id
1095 resource_id,
1096 file->etag(),
1097 "" /* title */);
1098
1099 base::MessageLoop::current()->PostTask(
1100 FROM_HERE,
1101 base::Bind(callback, HTTP_SUCCESS, session_url));
1102 return CancelCallback();
1103 }
1104
GetUploadStatus(const GURL & upload_url,int64 content_length,const UploadRangeCallback & callback)1105 CancelCallback FakeDriveService::GetUploadStatus(
1106 const GURL& upload_url,
1107 int64 content_length,
1108 const UploadRangeCallback& callback) {
1109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1110 DCHECK(!callback.is_null());
1111 return CancelCallback();
1112 }
1113
ResumeUpload(const GURL & upload_url,int64 start_position,int64 end_position,int64 content_length,const std::string & content_type,const base::FilePath & local_file_path,const UploadRangeCallback & callback,const ProgressCallback & progress_callback)1114 CancelCallback FakeDriveService::ResumeUpload(
1115 const GURL& upload_url,
1116 int64 start_position,
1117 int64 end_position,
1118 int64 content_length,
1119 const std::string& content_type,
1120 const base::FilePath& local_file_path,
1121 const UploadRangeCallback& callback,
1122 const ProgressCallback& progress_callback) {
1123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1124 DCHECK(!callback.is_null());
1125
1126 FileResourceCallback completion_callback
1127 = base::Bind(&ScheduleUploadRangeCallback,
1128 callback, start_position, end_position);
1129
1130 if (offline_) {
1131 completion_callback.Run(GDATA_NO_CONNECTION, scoped_ptr<FileResource>());
1132 return CancelCallback();
1133 }
1134
1135 if (!upload_sessions_.count(upload_url)) {
1136 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
1137 return CancelCallback();
1138 }
1139
1140 UploadSession* session = &upload_sessions_[upload_url];
1141
1142 // Chunks are required to be sent in such a ways that they fill from the start
1143 // of the not-yet-uploaded part with no gaps nor overlaps.
1144 if (session->uploaded_size != start_position) {
1145 completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<FileResource>());
1146 return CancelCallback();
1147 }
1148
1149 if (!progress_callback.is_null()) {
1150 // In the real GDataWapi/Drive DriveService, progress is reported in
1151 // nondeterministic timing. In this fake implementation, we choose to call
1152 // it twice per one ResumeUpload. This is for making sure that client code
1153 // works fine even if the callback is invoked more than once; it is the
1154 // crucial difference of the progress callback from others.
1155 // Note that progress is notified in the relative offset in each chunk.
1156 const int64 chunk_size = end_position - start_position;
1157 base::MessageLoop::current()->PostTask(
1158 FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size));
1159 base::MessageLoop::current()->PostTask(
1160 FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size));
1161 }
1162
1163 if (content_length != end_position) {
1164 session->uploaded_size = end_position;
1165 completion_callback.Run(HTTP_RESUME_INCOMPLETE, scoped_ptr<FileResource>());
1166 return CancelCallback();
1167 }
1168
1169 std::string content_data;
1170 if (!base::ReadFileToString(local_file_path, &content_data)) {
1171 session->uploaded_size = end_position;
1172 completion_callback.Run(GDATA_FILE_ERROR, scoped_ptr<FileResource>());
1173 return CancelCallback();
1174 }
1175 session->uploaded_size = end_position;
1176
1177 // |resource_id| is empty if the upload is for new file.
1178 if (session->resource_id.empty()) {
1179 DCHECK(!session->parent_resource_id.empty());
1180 DCHECK(!session->title.empty());
1181 const EntryInfo* new_entry = AddNewEntry(
1182 "", // auto generate resource id.
1183 session->content_type,
1184 content_data,
1185 session->parent_resource_id,
1186 session->title,
1187 false); // shared_with_me
1188 if (!new_entry) {
1189 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
1190 return CancelCallback();
1191 }
1192
1193 completion_callback.Run(HTTP_CREATED, make_scoped_ptr(
1194 new FileResource(*new_entry->change_resource.file())));
1195 base::MessageLoop::current()->PostTask(
1196 FROM_HERE,
1197 base::Bind(&FakeDriveService::NotifyObservers,
1198 weak_ptr_factory_.GetWeakPtr()));
1199 return CancelCallback();
1200 }
1201
1202 EntryInfo* entry = FindEntryByResourceId(session->resource_id);
1203 if (!entry) {
1204 completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>());
1205 return CancelCallback();
1206 }
1207
1208 ChangeResource* change = &entry->change_resource;
1209 FileResource* file = change->mutable_file();
1210 if (file->etag().empty() || session->etag != file->etag()) {
1211 completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<FileResource>());
1212 return CancelCallback();
1213 }
1214
1215 file->set_md5_checksum(base::MD5String(content_data));
1216 entry->content_data = content_data;
1217 file->set_file_size(end_position);
1218 AddNewChangestamp(change);
1219 UpdateETag(file);
1220
1221 completion_callback.Run(HTTP_SUCCESS, make_scoped_ptr(
1222 new FileResource(*file)));
1223 base::MessageLoop::current()->PostTask(
1224 FROM_HERE,
1225 base::Bind(&FakeDriveService::NotifyObservers,
1226 weak_ptr_factory_.GetWeakPtr()));
1227 return CancelCallback();
1228 }
1229
AuthorizeApp(const std::string & resource_id,const std::string & app_id,const AuthorizeAppCallback & callback)1230 CancelCallback FakeDriveService::AuthorizeApp(
1231 const std::string& resource_id,
1232 const std::string& app_id,
1233 const AuthorizeAppCallback& callback) {
1234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1235 DCHECK(!callback.is_null());
1236
1237 if (entries_.count(resource_id) == 0) {
1238 callback.Run(google_apis::HTTP_NOT_FOUND, GURL());
1239 return CancelCallback();
1240 }
1241
1242 callback.Run(HTTP_SUCCESS,
1243 GURL(base::StringPrintf(open_url_format_.c_str(),
1244 resource_id.c_str(),
1245 app_id.c_str())));
1246 return CancelCallback();
1247 }
1248
UninstallApp(const std::string & app_id,const google_apis::EntryActionCallback & callback)1249 CancelCallback FakeDriveService::UninstallApp(
1250 const std::string& app_id,
1251 const google_apis::EntryActionCallback& callback) {
1252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1253 DCHECK(!callback.is_null());
1254
1255 if (offline_) {
1256 base::MessageLoop::current()->PostTask(
1257 FROM_HERE,
1258 base::Bind(callback, google_apis::GDATA_NO_CONNECTION));
1259 return CancelCallback();
1260 }
1261
1262 // Find app_id from app_info_value_ and delete.
1263 base::ListValue* items = NULL;
1264 if (!app_info_value_->GetList("items", &items)) {
1265 base::MessageLoop::current()->PostTask(
1266 FROM_HERE,
1267 base::Bind(callback, google_apis::HTTP_NOT_FOUND));
1268 return CancelCallback();
1269 }
1270
1271 for (size_t i = 0; i < items->GetSize(); ++i) {
1272 base::DictionaryValue* item = NULL;
1273 std::string id;
1274 if (items->GetDictionary(i, &item) && item->GetString("id", &id) &&
1275 id == app_id) {
1276 base::MessageLoop::current()->PostTask(
1277 FROM_HERE,
1278 base::Bind(callback,
1279 items->Remove(i, NULL) ? google_apis::HTTP_NO_CONTENT
1280 : google_apis::HTTP_NOT_FOUND));
1281 return CancelCallback();
1282 }
1283 }
1284
1285 base::MessageLoop::current()->PostTask(
1286 FROM_HERE,
1287 base::Bind(callback, google_apis::HTTP_NOT_FOUND));
1288 return CancelCallback();
1289 }
1290
AddNewFile(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 FileResourceCallback & callback)1291 void FakeDriveService::AddNewFile(const std::string& content_type,
1292 const std::string& content_data,
1293 const std::string& parent_resource_id,
1294 const std::string& title,
1295 bool shared_with_me,
1296 const FileResourceCallback& callback) {
1297 AddNewFileWithResourceId("", content_type, content_data, parent_resource_id,
1298 title, shared_with_me, callback);
1299 }
1300
AddNewFileWithResourceId(const std::string & resource_id,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 FileResourceCallback & callback)1301 void FakeDriveService::AddNewFileWithResourceId(
1302 const std::string& resource_id,
1303 const std::string& content_type,
1304 const std::string& content_data,
1305 const std::string& parent_resource_id,
1306 const std::string& title,
1307 bool shared_with_me,
1308 const FileResourceCallback& callback) {
1309 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1310 DCHECK(!callback.is_null());
1311
1312 if (offline_) {
1313 base::MessageLoop::current()->PostTask(
1314 FROM_HERE,
1315 base::Bind(callback,
1316 GDATA_NO_CONNECTION,
1317 base::Passed(scoped_ptr<FileResource>())));
1318 return;
1319 }
1320
1321 const EntryInfo* new_entry = AddNewEntry(resource_id,
1322 content_type,
1323 content_data,
1324 parent_resource_id,
1325 title,
1326 shared_with_me);
1327 if (!new_entry) {
1328 base::MessageLoop::current()->PostTask(
1329 FROM_HERE,
1330 base::Bind(callback, HTTP_NOT_FOUND,
1331 base::Passed(scoped_ptr<FileResource>())));
1332 return;
1333 }
1334
1335 base::MessageLoop::current()->PostTask(
1336 FROM_HERE,
1337 base::Bind(callback, HTTP_CREATED,
1338 base::Passed(make_scoped_ptr(
1339 new FileResource(*new_entry->change_resource.file())))));
1340 base::MessageLoop::current()->PostTask(
1341 FROM_HERE,
1342 base::Bind(&FakeDriveService::NotifyObservers,
1343 weak_ptr_factory_.GetWeakPtr()));
1344 }
1345
AddNewDirectoryWithResourceId(const std::string & resource_id,const std::string & parent_resource_id,const std::string & directory_title,const AddNewDirectoryOptions & options,const FileResourceCallback & callback)1346 CancelCallback FakeDriveService::AddNewDirectoryWithResourceId(
1347 const std::string& resource_id,
1348 const std::string& parent_resource_id,
1349 const std::string& directory_title,
1350 const AddNewDirectoryOptions& options,
1351 const FileResourceCallback& callback) {
1352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1353 DCHECK(!callback.is_null());
1354
1355 if (offline_) {
1356 base::MessageLoop::current()->PostTask(
1357 FROM_HERE,
1358 base::Bind(callback,
1359 GDATA_NO_CONNECTION,
1360 base::Passed(scoped_ptr<FileResource>())));
1361 return CancelCallback();
1362 }
1363
1364 const EntryInfo* new_entry = AddNewEntry(resource_id,
1365 util::kDriveFolderMimeType,
1366 "", // content_data
1367 parent_resource_id,
1368 directory_title,
1369 false); // shared_with_me
1370 if (!new_entry) {
1371 base::MessageLoop::current()->PostTask(
1372 FROM_HERE,
1373 base::Bind(callback, HTTP_NOT_FOUND,
1374 base::Passed(scoped_ptr<FileResource>())));
1375 return CancelCallback();
1376 }
1377
1378 base::MessageLoop::current()->PostTask(
1379 FROM_HERE,
1380 base::Bind(callback, HTTP_CREATED,
1381 base::Passed(make_scoped_ptr(
1382 new FileResource(*new_entry->change_resource.file())))));
1383 base::MessageLoop::current()->PostTask(
1384 FROM_HERE,
1385 base::Bind(&FakeDriveService::NotifyObservers,
1386 weak_ptr_factory_.GetWeakPtr()));
1387 return CancelCallback();
1388 }
1389
SetLastModifiedTime(const std::string & resource_id,const base::Time & last_modified_time,const FileResourceCallback & callback)1390 void FakeDriveService::SetLastModifiedTime(
1391 const std::string& resource_id,
1392 const base::Time& last_modified_time,
1393 const FileResourceCallback& callback) {
1394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1395 DCHECK(!callback.is_null());
1396
1397 if (offline_) {
1398 base::MessageLoop::current()->PostTask(
1399 FROM_HERE,
1400 base::Bind(callback,
1401 GDATA_NO_CONNECTION,
1402 base::Passed(scoped_ptr<FileResource>())));
1403 return;
1404 }
1405
1406 EntryInfo* entry = FindEntryByResourceId(resource_id);
1407 if (!entry) {
1408 base::MessageLoop::current()->PostTask(
1409 FROM_HERE,
1410 base::Bind(callback, HTTP_NOT_FOUND,
1411 base::Passed(scoped_ptr<FileResource>())));
1412 return;
1413 }
1414
1415 ChangeResource* change = &entry->change_resource;
1416 FileResource* file = change->mutable_file();
1417 file->set_modified_date(last_modified_time);
1418
1419 base::MessageLoop::current()->PostTask(
1420 FROM_HERE,
1421 base::Bind(callback, HTTP_SUCCESS,
1422 base::Passed(make_scoped_ptr(new FileResource(*file)))));
1423 }
1424
SetUserPermission(const std::string & resource_id,google_apis::drive::PermissionRole user_permission)1425 google_apis::GDataErrorCode FakeDriveService::SetUserPermission(
1426 const std::string& resource_id,
1427 google_apis::drive::PermissionRole user_permission) {
1428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1429
1430 EntryInfo* entry = FindEntryByResourceId(resource_id);
1431 if (!entry)
1432 return HTTP_NOT_FOUND;
1433
1434 entry->user_permission = user_permission;
1435 return HTTP_SUCCESS;
1436 }
1437
AddChangeObserver(ChangeObserver * change_observer)1438 void FakeDriveService::AddChangeObserver(ChangeObserver* change_observer) {
1439 change_observers_.AddObserver(change_observer);
1440 }
1441
RemoveChangeObserver(ChangeObserver * change_observer)1442 void FakeDriveService::RemoveChangeObserver(ChangeObserver* change_observer) {
1443 change_observers_.RemoveObserver(change_observer);
1444 }
1445
FindEntryByResourceId(const std::string & resource_id)1446 FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId(
1447 const std::string& resource_id) {
1448 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1449
1450 EntryInfoMap::iterator it = entries_.find(resource_id);
1451 // Deleted entries don't have FileResource.
1452 return it != entries_.end() && it->second->change_resource.file() ?
1453 it->second : NULL;
1454 }
1455
GetNewResourceId()1456 std::string FakeDriveService::GetNewResourceId() {
1457 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1458
1459 ++resource_id_count_;
1460 return base::StringPrintf("resource_id_%d", resource_id_count_);
1461 }
1462
UpdateETag(google_apis::FileResource * file)1463 void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
1464 file->set_etag(
1465 "etag_" + base::Int64ToString(about_resource_->largest_change_id()));
1466 }
1467
AddNewChangestamp(google_apis::ChangeResource * change)1468 void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) {
1469 about_resource_->set_largest_change_id(
1470 about_resource_->largest_change_id() + 1);
1471 change->set_change_id(about_resource_->largest_change_id());
1472 }
1473
AddNewEntry(const std::string & given_resource_id,const std::string & content_type,const std::string & content_data,const std::string & parent_resource_id,const std::string & title,bool shared_with_me)1474 const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry(
1475 const std::string& given_resource_id,
1476 const std::string& content_type,
1477 const std::string& content_data,
1478 const std::string& parent_resource_id,
1479 const std::string& title,
1480 bool shared_with_me) {
1481 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1482
1483 if (!parent_resource_id.empty() &&
1484 parent_resource_id != GetRootResourceId() &&
1485 !entries_.count(parent_resource_id)) {
1486 return NULL;
1487 }
1488
1489 const std::string resource_id =
1490 given_resource_id.empty() ? GetNewResourceId() : given_resource_id;
1491 if (entries_.count(resource_id))
1492 return NULL;
1493 GURL upload_url = GURL("https://xxx/upload/" + resource_id);
1494
1495 scoped_ptr<EntryInfo> new_entry(new EntryInfo);
1496 ChangeResource* new_change = &new_entry->change_resource;
1497 FileResource* new_file = new FileResource;
1498 new_change->set_file(make_scoped_ptr(new_file));
1499
1500 // Set the resource ID and the title
1501 new_change->set_file_id(resource_id);
1502 new_file->set_file_id(resource_id);
1503 new_file->set_title(title);
1504 // Set the contents, size and MD5 for a file.
1505 if (content_type != util::kDriveFolderMimeType &&
1506 !util::IsKnownHostedDocumentMimeType(content_type)) {
1507 new_entry->content_data = content_data;
1508 new_file->set_file_size(content_data.size());
1509 new_file->set_md5_checksum(base::MD5String(content_data));
1510 }
1511
1512 if (shared_with_me) {
1513 // Set current time to mark the file as shared_with_me.
1514 new_file->set_shared_with_me_date(base::Time::Now());
1515 }
1516
1517 std::string escaped_resource_id = net::EscapePath(resource_id);
1518
1519 // Set mime type.
1520 new_file->set_mime_type(content_type);
1521
1522 // Set alternate link if needed.
1523 if (content_type == util::kGoogleDocumentMimeType)
1524 new_file->set_alternate_link(GURL("https://document_alternate_link"));
1525
1526 // Set parents.
1527 if (!parent_resource_id.empty()) {
1528 ParentReference parent;
1529 parent.set_file_id(parent_resource_id);
1530 parent.set_parent_link(GetFakeLinkUrl(parent.file_id()));
1531 std::vector<ParentReference> parents;
1532 parents.push_back(parent);
1533 *new_file->mutable_parents() = parents;
1534 }
1535
1536 new_entry->share_url = net::AppendOrReplaceQueryParameter(
1537 share_url_base_, "name", title);
1538
1539 AddNewChangestamp(new_change);
1540 UpdateETag(new_file);
1541
1542 base::Time published_date =
1543 base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_);
1544 new_file->set_created_date(published_date);
1545
1546 EntryInfo* raw_new_entry = new_entry.release();
1547 entries_[resource_id] = raw_new_entry;
1548 return raw_new_entry;
1549 }
1550
GetChangeListInternal(int64 start_changestamp,const std::string & search_query,const std::string & directory_resource_id,int start_offset,int max_results,int * load_counter,const ChangeListCallback & callback)1551 void FakeDriveService::GetChangeListInternal(
1552 int64 start_changestamp,
1553 const std::string& search_query,
1554 const std::string& directory_resource_id,
1555 int start_offset,
1556 int max_results,
1557 int* load_counter,
1558 const ChangeListCallback& callback) {
1559 if (offline_) {
1560 base::MessageLoop::current()->PostTask(
1561 FROM_HERE,
1562 base::Bind(callback,
1563 GDATA_NO_CONNECTION,
1564 base::Passed(scoped_ptr<ChangeList>())));
1565 return;
1566 }
1567
1568 // Filter out entries per parameters like |directory_resource_id| and
1569 // |search_query|.
1570 ScopedVector<ChangeResource> entries;
1571 int num_entries_matched = 0;
1572 for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end();
1573 ++it) {
1574 const ChangeResource& entry = it->second->change_resource;
1575 bool should_exclude = false;
1576
1577 // If |directory_resource_id| is set, exclude the entry if it's not in
1578 // the target directory.
1579 if (!directory_resource_id.empty()) {
1580 // Get the parent resource ID of the entry.
1581 std::string parent_resource_id;
1582 if (entry.file() && !entry.file()->parents().empty())
1583 parent_resource_id = entry.file()->parents()[0].file_id();
1584
1585 if (directory_resource_id != parent_resource_id)
1586 should_exclude = true;
1587 }
1588
1589 // If |search_query| is set, exclude the entry if it does not contain the
1590 // search query in the title.
1591 if (!should_exclude && !search_query.empty() &&
1592 !EntryMatchWithQuery(entry, search_query)) {
1593 should_exclude = true;
1594 }
1595
1596 // If |start_changestamp| is set, exclude the entry if the
1597 // changestamp is older than |largest_changestamp|.
1598 // See https://developers.google.com/google-apps/documents-list/
1599 // #retrieving_all_changes_since_a_given_changestamp
1600 if (start_changestamp > 0 && entry.change_id() < start_changestamp)
1601 should_exclude = true;
1602
1603 // If the caller requests other list than change list by specifying
1604 // zero-|start_changestamp|, exclude deleted entry from the result.
1605 const bool deleted = entry.is_deleted() ||
1606 (entry.file() && entry.file()->labels().is_trashed());
1607 if (!start_changestamp && deleted)
1608 should_exclude = true;
1609
1610 // The entry matched the criteria for inclusion.
1611 if (!should_exclude)
1612 ++num_entries_matched;
1613
1614 // If |start_offset| is set, exclude the entry if the entry is before the
1615 // start index. <= instead of < as |num_entries_matched| was
1616 // already incremented.
1617 if (start_offset > 0 && num_entries_matched <= start_offset)
1618 should_exclude = true;
1619
1620 if (!should_exclude) {
1621 scoped_ptr<ChangeResource> entry_copied(new ChangeResource);
1622 entry_copied->set_change_id(entry.change_id());
1623 entry_copied->set_file_id(entry.file_id());
1624 entry_copied->set_deleted(entry.is_deleted());
1625 if (entry.file()) {
1626 entry_copied->set_file(
1627 make_scoped_ptr(new FileResource(*entry.file())));
1628 }
1629 entry_copied->set_modification_date(entry.modification_date());
1630 entries.push_back(entry_copied.release());
1631 }
1632 }
1633
1634 scoped_ptr<ChangeList> change_list(new ChangeList);
1635 if (start_changestamp > 0 && start_offset == 0) {
1636 change_list->set_largest_change_id(about_resource_->largest_change_id());
1637 }
1638
1639 // If |max_results| is set, trim the entries if the number exceeded the max
1640 // results.
1641 if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) {
1642 entries.erase(entries.begin() + max_results, entries.end());
1643 // Adds the next URL.
1644 // Here, we embed information which is needed for continuing the
1645 // GetChangeList request in the next invocation into url query
1646 // parameters.
1647 GURL next_url(base::StringPrintf(
1648 "http://localhost/?start-offset=%d&max-results=%d",
1649 start_offset + max_results,
1650 max_results));
1651 if (start_changestamp > 0) {
1652 next_url = net::AppendOrReplaceQueryParameter(
1653 next_url, "changestamp",
1654 base::Int64ToString(start_changestamp).c_str());
1655 }
1656 if (!search_query.empty()) {
1657 next_url = net::AppendOrReplaceQueryParameter(
1658 next_url, "q", search_query);
1659 }
1660 if (!directory_resource_id.empty()) {
1661 next_url = net::AppendOrReplaceQueryParameter(
1662 next_url, "parent", directory_resource_id);
1663 }
1664
1665 change_list->set_next_link(next_url);
1666 }
1667 *change_list->mutable_items() = entries.Pass();
1668
1669 if (load_counter)
1670 *load_counter += 1;
1671 base::MessageLoop::current()->PostTask(
1672 FROM_HERE,
1673 base::Bind(callback, HTTP_SUCCESS, base::Passed(&change_list)));
1674 }
1675
GetNewUploadSessionUrl()1676 GURL FakeDriveService::GetNewUploadSessionUrl() {
1677 return GURL("https://upload_session_url/" +
1678 base::Int64ToString(next_upload_sequence_number_++));
1679 }
1680
AddPermission(const std::string & resource_id,const std::string & email,google_apis::drive::PermissionRole role,const google_apis::EntryActionCallback & callback)1681 google_apis::CancelCallback FakeDriveService::AddPermission(
1682 const std::string& resource_id,
1683 const std::string& email,
1684 google_apis::drive::PermissionRole role,
1685 const google_apis::EntryActionCallback& callback) {
1686 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1687 DCHECK(!callback.is_null());
1688
1689 NOTREACHED();
1690 return CancelCallback();
1691 }
1692
NotifyObservers()1693 void FakeDriveService::NotifyObservers() {
1694 FOR_EACH_OBSERVER(ChangeObserver, change_observers_, OnNewChangeAvailable());
1695 }
1696
1697 } // namespace drive
1698