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