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