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