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