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