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