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