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