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