fake_drive_service.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
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
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_NOT_FOUND;
54using google_apis::HTTP_NO_CONTENT;
55using google_apis::HTTP_PRECONDITION;
56using google_apis::HTTP_RESUME_INCOMPLETE;
57using google_apis::HTTP_SUCCESS;
58using google_apis::InitiateUploadCallback;
59using google_apis::ParentReference;
60using google_apis::ProgressCallback;
61using google_apis::UploadRangeResponse;
62using google_apis::drive::UploadRangeCallback;
63namespace test_util = google_apis::test_util;
64
65namespace drive {
66namespace {
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.
73bool 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
104void 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
118void EntryActionCallbackAdapter(
119    const EntryActionCallback& callback,
120    GDataErrorCode error, scoped_ptr<FileResource> file) {
121  callback.Run(error);
122}
123
124void 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
142struct FakeDriveService::EntryInfo {
143  google_apis::ChangeResource change_resource;
144  GURL share_url;
145  std::string content_data;
146};
147
148struct 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
158  UploadSession()
159      : content_length(0),
160        uploaded_size(0) {}
161
162  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
179FakeDriveService::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
202FakeDriveService::~FakeDriveService() {
203  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
204  STLDeleteValues(&entries_);
205}
206
207bool 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
219void 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
245void 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
261bool 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
278void 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
285GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) {
286  return GURL("https://fake_server/" + net::EscapePath(resource_id));
287}
288
289void FakeDriveService::Initialize(const std::string& account_id) {
290  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
291}
292
293void FakeDriveService::AddObserver(DriveServiceObserver* observer) {
294  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
295}
296
297void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) {
298  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299}
300
301bool FakeDriveService::CanSendRequest() const {
302  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
303  return true;
304}
305
306ResourceIdCanonicalizer FakeDriveService::GetResourceIdCanonicalizer() const {
307  return util::GetIdentityResourceIdCanonicalizer();
308}
309
310bool FakeDriveService::HasAccessToken() const {
311  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312  return true;
313}
314
315void 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
321bool FakeDriveService::HasRefreshToken() const {
322  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323  return true;
324}
325
326void FakeDriveService::ClearAccessToken() {
327  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
328}
329
330void FakeDriveService::ClearRefreshToken() {
331  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332}
333
334std::string FakeDriveService::GetRootResourceId() const {
335  return "fake_root";
336}
337
338CancelCallback 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
358CancelCallback 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
375CancelCallback 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
392CancelCallback 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
412CancelCallback 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
428CancelCallback 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
475CancelCallback 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
486CancelCallback 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
517CancelCallback 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
547CancelCallback 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
570CancelCallback 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
593CancelCallback 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
635CancelCallback 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
669CancelCallback 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
740CancelCallback 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
808CancelCallback 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
867CancelCallback 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
879CancelCallback 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
915CancelCallback 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
949CancelCallback 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
962CancelCallback 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
1001CancelCallback 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
1048CancelCallback 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
1057CancelCallback 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
1165CancelCallback 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
1174CancelCallback 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
1205void 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
1215void 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
1256CancelCallback 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
1296void 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
1331FakeDriveService::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
1341std::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
1348void FakeDriveService::UpdateETag(google_apis::FileResource* file) {
1349  file->set_etag(
1350      "etag_" + base::Int64ToString(about_resource_->largest_change_id()));
1351}
1352
1353void 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
1359const 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
1435void 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
1560GURL FakeDriveService::GetNewUploadSessionUrl() {
1561  return GURL("https://upload_session_url/" +
1562              base::Int64ToString(next_upload_sequence_number_++));
1563}
1564
1565google_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