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