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