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/chromeos/drive/job_scheduler.h"
      6 
      7 #include <set>
      8 
      9 #include "base/bind.h"
     10 #include "base/file_util.h"
     11 #include "base/files/scoped_temp_dir.h"
     12 #include "base/prefs/testing_pref_service.h"
     13 #include "base/run_loop.h"
     14 #include "base/stl_util.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "chrome/browser/chromeos/drive/test_util.h"
     17 #include "chrome/browser/drive/event_logger.h"
     18 #include "chrome/browser/drive/fake_drive_service.h"
     19 #include "chrome/browser/drive/test_util.h"
     20 #include "chrome/common/pref_names.h"
     21 #include "content/public/test/test_browser_thread_bundle.h"
     22 #include "google_apis/drive/drive_api_parser.h"
     23 #include "google_apis/drive/test_util.h"
     24 #include "testing/gtest/include/gtest/gtest.h"
     25 
     26 namespace drive {
     27 
     28 namespace {
     29 
     30 // Dummy value passed for the |expected_file_size| parameter of DownloadFile().
     31 const int64 kDummyDownloadFileSize = 0;
     32 
     33 void CopyTitleFromFileResourceCallback(
     34     std::vector<std::string>* title_list_out,
     35     google_apis::GDataErrorCode error_in,
     36     scoped_ptr<google_apis::FileResource> entry_in) {
     37   title_list_out->push_back(entry_in->title());
     38 }
     39 
     40 class JobListLogger : public JobListObserver {
     41  public:
     42   enum EventType {
     43     ADDED,
     44     UPDATED,
     45     DONE,
     46   };
     47 
     48   struct EventLog {
     49     EventType type;
     50     JobInfo info;
     51 
     52     EventLog(EventType type, const JobInfo& info) : type(type), info(info) {
     53     }
     54   };
     55 
     56   // Checks whether the specified type of event has occurred.
     57   bool Has(EventType type, JobType job_type) {
     58     for (size_t i = 0; i < events.size(); ++i) {
     59       if (events[i].type == type && events[i].info.job_type == job_type)
     60         return true;
     61     }
     62     return false;
     63   }
     64 
     65   // Gets the progress event information of the specified type.
     66   void GetProgressInfo(JobType job_type, std::vector<int64>* progress) {
     67     for (size_t i = 0; i < events.size(); ++i) {
     68       if (events[i].type == UPDATED && events[i].info.job_type == job_type)
     69         progress->push_back(events[i].info.num_completed_bytes);
     70     }
     71   }
     72 
     73   // JobListObserver overrides.
     74   virtual void OnJobAdded(const JobInfo& info) OVERRIDE {
     75     events.push_back(EventLog(ADDED, info));
     76   }
     77 
     78   virtual void OnJobUpdated(const JobInfo& info) OVERRIDE {
     79     events.push_back(EventLog(UPDATED, info));
     80   }
     81 
     82   virtual void OnJobDone(const JobInfo& info, FileError error) OVERRIDE {
     83     events.push_back(EventLog(DONE, info));
     84   }
     85 
     86  private:
     87   std::vector<EventLog> events;
     88 };
     89 
     90 // Fake drive service extended for testing cancellation.
     91 // When upload_new_file_cancelable is set, this Drive service starts
     92 // returning a closure to cancel from InitiateUploadNewFile(). The task will
     93 // finish only when the cancel closure is called.
     94 class CancelTestableFakeDriveService : public FakeDriveService {
     95  public:
     96   CancelTestableFakeDriveService()
     97       : upload_new_file_cancelable_(false) {
     98   }
     99 
    100   void set_upload_new_file_cancelable(bool cancelable) {
    101     upload_new_file_cancelable_ = cancelable;
    102   }
    103 
    104   virtual google_apis::CancelCallback InitiateUploadNewFile(
    105       const std::string& content_type,
    106       int64 content_length,
    107       const std::string& parent_resource_id,
    108       const std::string& title,
    109       const InitiateUploadNewFileOptions& options,
    110       const google_apis::InitiateUploadCallback& callback) OVERRIDE {
    111     if (upload_new_file_cancelable_)
    112       return base::Bind(callback, google_apis::GDATA_CANCELLED, GURL());
    113 
    114     return FakeDriveService::InitiateUploadNewFile(content_type,
    115                                                    content_length,
    116                                                    parent_resource_id,
    117                                                    title,
    118                                                    options,
    119                                                    callback);
    120   }
    121 
    122  private:
    123   bool upload_new_file_cancelable_;
    124 };
    125 
    126 }  // namespace
    127 
    128 class JobSchedulerTest : public testing::Test {
    129  public:
    130   JobSchedulerTest()
    131       : pref_service_(new TestingPrefServiceSimple) {
    132     test_util::RegisterDrivePrefs(pref_service_->registry());
    133   }
    134 
    135   virtual void SetUp() OVERRIDE {
    136     fake_network_change_notifier_.reset(
    137         new test_util::FakeNetworkChangeNotifier);
    138 
    139     logger_.reset(new EventLogger);
    140 
    141     fake_drive_service_.reset(new CancelTestableFakeDriveService);
    142     test_util::SetUpTestEntries(fake_drive_service_.get());
    143     fake_drive_service_->LoadAppListForDriveApi("drive/applist.json");
    144 
    145     scheduler_.reset(new JobScheduler(pref_service_.get(),
    146                                       logger_.get(),
    147                                       fake_drive_service_.get(),
    148                                       base::MessageLoopProxy::current().get()));
    149     scheduler_->SetDisableThrottling(true);
    150   }
    151 
    152  protected:
    153   // Sets up FakeNetworkChangeNotifier as if it's connected to a network with
    154   // the specified connection type.
    155   void ChangeConnectionType(net::NetworkChangeNotifier::ConnectionType type) {
    156     fake_network_change_notifier_->SetConnectionType(type);
    157   }
    158 
    159   // Sets up FakeNetworkChangeNotifier as if it's connected to wifi network.
    160   void ConnectToWifi() {
    161     ChangeConnectionType(net::NetworkChangeNotifier::CONNECTION_WIFI);
    162   }
    163 
    164   // Sets up FakeNetworkChangeNotifier as if it's connected to cellular network.
    165   void ConnectToCellular() {
    166     ChangeConnectionType(net::NetworkChangeNotifier::CONNECTION_2G);
    167   }
    168 
    169   // Sets up FakeNetworkChangeNotifier as if it's connected to wimax network.
    170   void ConnectToWimax() {
    171     ChangeConnectionType(net::NetworkChangeNotifier::CONNECTION_4G);
    172   }
    173 
    174   // Sets up FakeNetworkChangeNotifier as if it's disconnected.
    175   void ConnectToNone() {
    176     ChangeConnectionType(net::NetworkChangeNotifier::CONNECTION_NONE);
    177   }
    178 
    179   static int GetMetadataQueueMaxJobCount() {
    180     return JobScheduler::kMaxJobCount[JobScheduler::METADATA_QUEUE];
    181   }
    182 
    183   content::TestBrowserThreadBundle thread_bundle_;
    184   scoped_ptr<TestingPrefServiceSimple> pref_service_;
    185   scoped_ptr<test_util::FakeNetworkChangeNotifier>
    186       fake_network_change_notifier_;
    187   scoped_ptr<EventLogger> logger_;
    188   scoped_ptr<CancelTestableFakeDriveService> fake_drive_service_;
    189   scoped_ptr<JobScheduler> scheduler_;
    190 };
    191 
    192 TEST_F(JobSchedulerTest, GetAboutResource) {
    193   ConnectToWifi();
    194 
    195   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    196   scoped_ptr<google_apis::AboutResource> about_resource;
    197   scheduler_->GetAboutResource(
    198       google_apis::test_util::CreateCopyResultCallback(
    199           &error, &about_resource));
    200   base::RunLoop().RunUntilIdle();
    201   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    202   ASSERT_TRUE(about_resource);
    203 }
    204 
    205 TEST_F(JobSchedulerTest, GetAppList) {
    206   ConnectToWifi();
    207 
    208   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    209   scoped_ptr<google_apis::AppList> app_list;
    210 
    211   scheduler_->GetAppList(
    212       google_apis::test_util::CreateCopyResultCallback(&error, &app_list));
    213   base::RunLoop().RunUntilIdle();
    214 
    215   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    216   ASSERT_TRUE(app_list);
    217 }
    218 
    219 TEST_F(JobSchedulerTest, GetAllFileList) {
    220   ConnectToWifi();
    221 
    222   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    223   scoped_ptr<google_apis::FileList> file_list;
    224 
    225   scheduler_->GetAllFileList(
    226       google_apis::test_util::CreateCopyResultCallback(&error, &file_list));
    227   base::RunLoop().RunUntilIdle();
    228 
    229   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    230   ASSERT_TRUE(file_list);
    231 }
    232 
    233 TEST_F(JobSchedulerTest, GetFileListInDirectory) {
    234   ConnectToWifi();
    235 
    236   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    237   scoped_ptr<google_apis::FileList> file_list;
    238 
    239   scheduler_->GetFileListInDirectory(
    240       fake_drive_service_->GetRootResourceId(),
    241       google_apis::test_util::CreateCopyResultCallback(&error, &file_list));
    242   base::RunLoop().RunUntilIdle();
    243 
    244   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    245   ASSERT_TRUE(file_list);
    246 }
    247 
    248 TEST_F(JobSchedulerTest, Search) {
    249   ConnectToWifi();
    250 
    251   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    252   scoped_ptr<google_apis::FileList> file_list;
    253 
    254   scheduler_->Search(
    255       "File",  // search query
    256       google_apis::test_util::CreateCopyResultCallback(&error, &file_list));
    257   base::RunLoop().RunUntilIdle();
    258 
    259   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    260   ASSERT_TRUE(file_list);
    261 }
    262 
    263 TEST_F(JobSchedulerTest, GetChangeList) {
    264   ConnectToWifi();
    265 
    266   int64 old_largest_change_id =
    267       fake_drive_service_->about_resource().largest_change_id();
    268 
    269   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    270 
    271   // Create a new directory.
    272   {
    273     scoped_ptr<google_apis::FileResource> entry;
    274     fake_drive_service_->AddNewDirectory(
    275         fake_drive_service_->GetRootResourceId(),
    276         "new directory",
    277         DriveServiceInterface::AddNewDirectoryOptions(),
    278         google_apis::test_util::CreateCopyResultCallback(
    279             &error, &entry));
    280     base::RunLoop().RunUntilIdle();
    281     ASSERT_EQ(google_apis::HTTP_CREATED, error);
    282   }
    283 
    284   error = google_apis::GDATA_OTHER_ERROR;
    285   scoped_ptr<google_apis::ChangeList> change_list;
    286   scheduler_->GetChangeList(
    287       old_largest_change_id + 1,
    288       google_apis::test_util::CreateCopyResultCallback(&error, &change_list));
    289   base::RunLoop().RunUntilIdle();
    290 
    291   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    292   ASSERT_TRUE(change_list);
    293 }
    294 
    295 TEST_F(JobSchedulerTest, GetRemainingChangeList) {
    296   ConnectToWifi();
    297   fake_drive_service_->set_default_max_results(2);
    298 
    299   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    300   scoped_ptr<google_apis::ChangeList> change_list;
    301 
    302   scheduler_->GetChangeList(
    303       0,
    304       google_apis::test_util::CreateCopyResultCallback(&error, &change_list));
    305   base::RunLoop().RunUntilIdle();
    306 
    307   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    308   ASSERT_TRUE(change_list);
    309 
    310   // Keep the next url before releasing the |change_list|.
    311   GURL next_url(change_list->next_link());
    312 
    313   error = google_apis::GDATA_OTHER_ERROR;
    314   change_list.reset();
    315 
    316   scheduler_->GetRemainingChangeList(
    317       next_url,
    318       google_apis::test_util::CreateCopyResultCallback(&error, &change_list));
    319   base::RunLoop().RunUntilIdle();
    320 
    321   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    322   ASSERT_TRUE(change_list);
    323 }
    324 
    325 TEST_F(JobSchedulerTest, GetRemainingFileList) {
    326   ConnectToWifi();
    327   fake_drive_service_->set_default_max_results(2);
    328 
    329   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    330   scoped_ptr<google_apis::FileList> file_list;
    331 
    332   scheduler_->GetFileListInDirectory(
    333       fake_drive_service_->GetRootResourceId(),
    334       google_apis::test_util::CreateCopyResultCallback(&error, &file_list));
    335   base::RunLoop().RunUntilIdle();
    336 
    337   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    338   ASSERT_TRUE(file_list);
    339 
    340   // Keep the next url before releasing the |file_list|.
    341   GURL next_url(file_list->next_link());
    342 
    343   error = google_apis::GDATA_OTHER_ERROR;
    344   file_list.reset();
    345 
    346   scheduler_->GetRemainingFileList(
    347       next_url,
    348       google_apis::test_util::CreateCopyResultCallback(&error, &file_list));
    349   base::RunLoop().RunUntilIdle();
    350 
    351   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    352   ASSERT_TRUE(file_list);
    353 }
    354 
    355 TEST_F(JobSchedulerTest, GetFileResource) {
    356   ConnectToWifi();
    357 
    358   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    359   scoped_ptr<google_apis::FileResource> entry;
    360 
    361   scheduler_->GetFileResource(
    362       "file:2_file_resource_id",  // resource ID
    363       ClientContext(USER_INITIATED),
    364       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    365   base::RunLoop().RunUntilIdle();
    366 
    367   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    368   ASSERT_TRUE(entry);
    369 }
    370 
    371 TEST_F(JobSchedulerTest, GetShareUrl) {
    372   ConnectToWifi();
    373 
    374   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    375   GURL share_url;
    376 
    377   scheduler_->GetShareUrl(
    378       "file:2_file_resource_id",  // resource ID
    379       GURL("chrome-extension://test-id/"), // embed origin
    380       ClientContext(USER_INITIATED),
    381       google_apis::test_util::CreateCopyResultCallback(&error, &share_url));
    382   base::RunLoop().RunUntilIdle();
    383 
    384   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    385   ASSERT_FALSE(share_url.is_empty());
    386 }
    387 
    388 TEST_F(JobSchedulerTest, TrashResource) {
    389   ConnectToWifi();
    390 
    391   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    392 
    393   scheduler_->TrashResource(
    394       "file:2_file_resource_id",
    395       ClientContext(USER_INITIATED),
    396       google_apis::test_util::CreateCopyResultCallback(&error));
    397   base::RunLoop().RunUntilIdle();
    398 
    399   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    400 }
    401 
    402 TEST_F(JobSchedulerTest, CopyResource) {
    403   ConnectToWifi();
    404 
    405   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    406   scoped_ptr<google_apis::FileResource> entry;
    407 
    408   scheduler_->CopyResource(
    409       "file:2_file_resource_id",  // resource ID
    410       "folder:1_folder_resource_id",  // parent resource ID
    411       "New Document",  // new title
    412       base::Time(),
    413       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    414   base::RunLoop().RunUntilIdle();
    415 
    416   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    417   ASSERT_TRUE(entry);
    418 }
    419 
    420 TEST_F(JobSchedulerTest, UpdateResource) {
    421   ConnectToWifi();
    422 
    423   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    424   scoped_ptr<google_apis::FileResource> entry;
    425 
    426   scheduler_->UpdateResource(
    427       "file:2_file_resource_id",  // resource ID
    428       "folder:1_folder_resource_id",  // parent resource ID
    429       "New Document",  // new title
    430       base::Time(),
    431       base::Time(),
    432       ClientContext(USER_INITIATED),
    433       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    434   base::RunLoop().RunUntilIdle();
    435 
    436   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    437   ASSERT_TRUE(entry);
    438 }
    439 
    440 TEST_F(JobSchedulerTest, RenameResource) {
    441   ConnectToWifi();
    442 
    443   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    444 
    445   scheduler_->RenameResource(
    446       "file:2_file_resource_id",
    447       "New Title",
    448       google_apis::test_util::CreateCopyResultCallback(&error));
    449   base::RunLoop().RunUntilIdle();
    450 
    451   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    452 }
    453 
    454 TEST_F(JobSchedulerTest, AddResourceToDirectory) {
    455   ConnectToWifi();
    456 
    457   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    458 
    459   scheduler_->AddResourceToDirectory(
    460       "folder:1_folder_resource_id",
    461       "file:2_file_resource_id",
    462       google_apis::test_util::CreateCopyResultCallback(&error));
    463   base::RunLoop().RunUntilIdle();
    464 
    465   ASSERT_EQ(google_apis::HTTP_SUCCESS, error);
    466 }
    467 
    468 TEST_F(JobSchedulerTest, RemoveResourceFromDirectory) {
    469   ConnectToWifi();
    470 
    471   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    472 
    473   scheduler_->RemoveResourceFromDirectory(
    474       "folder:1_folder_resource_id",
    475       "file:subdirectory_file_1_id",  // resource ID
    476       ClientContext(USER_INITIATED),
    477       google_apis::test_util::CreateCopyResultCallback(&error));
    478   base::RunLoop().RunUntilIdle();
    479 
    480   ASSERT_EQ(google_apis::HTTP_NO_CONTENT, error);
    481 }
    482 
    483 TEST_F(JobSchedulerTest, AddNewDirectory) {
    484   ConnectToWifi();
    485 
    486   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    487   scoped_ptr<google_apis::FileResource> entry;
    488 
    489   scheduler_->AddNewDirectory(
    490       fake_drive_service_->GetRootResourceId(),  // Root directory.
    491       "New Directory",
    492       DriveServiceInterface::AddNewDirectoryOptions(),
    493       ClientContext(USER_INITIATED),
    494       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    495   base::RunLoop().RunUntilIdle();
    496 
    497   ASSERT_EQ(google_apis::HTTP_CREATED, error);
    498   ASSERT_TRUE(entry);
    499 }
    500 
    501 TEST_F(JobSchedulerTest, PriorityHandling) {
    502   // Saturate the metadata job queue with uninteresting jobs to prevent
    503   // following jobs from starting.
    504   google_apis::GDataErrorCode error_dontcare = google_apis::GDATA_OTHER_ERROR;
    505   scoped_ptr<google_apis::FileResource> entry_dontcare;
    506   for (int i = 0; i < GetMetadataQueueMaxJobCount(); ++i) {
    507     std::string resource_id("file:2_file_resource_id");
    508     scheduler_->GetFileResource(
    509         resource_id,
    510         ClientContext(USER_INITIATED),
    511         google_apis::test_util::CreateCopyResultCallback(&error_dontcare,
    512                                                          &entry_dontcare));
    513   }
    514 
    515   // Start jobs with different priorities.
    516   std::string title_1("new file 1");
    517   std::string title_2("new file 2");
    518   std::string title_3("new file 3");
    519   std::string title_4("new file 4");
    520   std::vector<std::string> titles;
    521 
    522   scheduler_->AddNewDirectory(
    523       fake_drive_service_->GetRootResourceId(),
    524       title_1,
    525       DriveServiceInterface::AddNewDirectoryOptions(),
    526       ClientContext(USER_INITIATED),
    527       base::Bind(&CopyTitleFromFileResourceCallback, &titles));
    528   scheduler_->AddNewDirectory(
    529       fake_drive_service_->GetRootResourceId(),
    530       title_2,
    531       DriveServiceInterface::AddNewDirectoryOptions(),
    532       ClientContext(BACKGROUND),
    533       base::Bind(&CopyTitleFromFileResourceCallback, &titles));
    534   scheduler_->AddNewDirectory(
    535       fake_drive_service_->GetRootResourceId(),
    536       title_3,
    537       DriveServiceInterface::AddNewDirectoryOptions(),
    538       ClientContext(BACKGROUND),
    539       base::Bind(&CopyTitleFromFileResourceCallback, &titles));
    540   scheduler_->AddNewDirectory(
    541       fake_drive_service_->GetRootResourceId(),
    542       title_4,
    543       DriveServiceInterface::AddNewDirectoryOptions(),
    544       ClientContext(USER_INITIATED),
    545       base::Bind(&CopyTitleFromFileResourceCallback, &titles));
    546 
    547   base::RunLoop().RunUntilIdle();
    548 
    549   ASSERT_EQ(4ul, titles.size());
    550   EXPECT_EQ(title_1, titles[0]);
    551   EXPECT_EQ(title_4, titles[1]);
    552   EXPECT_EQ(title_2, titles[2]);
    553   EXPECT_EQ(title_3, titles[3]);
    554 }
    555 
    556 TEST_F(JobSchedulerTest, NoConnectionUserInitiated) {
    557   ConnectToNone();
    558 
    559   std::string resource_id("file:2_file_resource_id");
    560 
    561   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    562   scoped_ptr<google_apis::FileResource> entry;
    563   scheduler_->GetFileResource(
    564       resource_id,
    565       ClientContext(USER_INITIATED),
    566       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    567   base::RunLoop().RunUntilIdle();
    568 
    569   EXPECT_EQ(google_apis::GDATA_NO_CONNECTION, error);
    570 }
    571 
    572 TEST_F(JobSchedulerTest, NoConnectionBackground) {
    573   ConnectToNone();
    574 
    575   std::string resource_id("file:2_file_resource_id");
    576 
    577   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    578   scoped_ptr<google_apis::FileResource> entry;
    579   scheduler_->GetFileResource(
    580       resource_id,
    581       ClientContext(BACKGROUND),
    582       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    583   base::RunLoop().RunUntilIdle();
    584 
    585   EXPECT_FALSE(entry);
    586 
    587   // Reconnect to the net.
    588   ConnectToWifi();
    589 
    590   base::RunLoop().RunUntilIdle();
    591 
    592   EXPECT_EQ(google_apis::HTTP_SUCCESS, error);
    593   ASSERT_TRUE(entry);
    594 }
    595 
    596 TEST_F(JobSchedulerTest, DownloadFileCellularDisabled) {
    597   ConnectToCellular();
    598 
    599   // Disable fetching over cellular network.
    600   pref_service_->SetBoolean(prefs::kDisableDriveOverCellular, true);
    601 
    602   // Try to get a file in the background
    603   base::ScopedTempDir temp_dir;
    604   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    605 
    606   const base::FilePath kOutputFilePath =
    607       temp_dir.path().AppendASCII("whatever.txt");
    608   google_apis::GDataErrorCode download_error = google_apis::GDATA_OTHER_ERROR;
    609   base::FilePath output_file_path;
    610   scheduler_->DownloadFile(
    611       base::FilePath::FromUTF8Unsafe("drive/whatever.txt"),  // virtual path
    612       kDummyDownloadFileSize,
    613       kOutputFilePath,
    614       "file:2_file_resource_id",
    615       ClientContext(BACKGROUND),
    616       google_apis::test_util::CreateCopyResultCallback(
    617           &download_error, &output_file_path),
    618       google_apis::GetContentCallback());
    619   // Metadata should still work
    620   google_apis::GDataErrorCode metadata_error = google_apis::GDATA_OTHER_ERROR;
    621   scoped_ptr<google_apis::AboutResource> about_resource;
    622 
    623   // Try to get the metadata
    624   scheduler_->GetAboutResource(
    625       google_apis::test_util::CreateCopyResultCallback(
    626           &metadata_error, &about_resource));
    627   base::RunLoop().RunUntilIdle();
    628 
    629   // Check the metadata
    630   ASSERT_EQ(google_apis::HTTP_SUCCESS, metadata_error);
    631   ASSERT_TRUE(about_resource);
    632 
    633   // Check the download
    634   EXPECT_EQ(google_apis::GDATA_OTHER_ERROR, download_error);
    635 
    636   // Switch to a Wifi connection
    637   ConnectToWifi();
    638 
    639   base::RunLoop().RunUntilIdle();
    640 
    641   // Check the download again
    642   EXPECT_EQ(google_apis::HTTP_SUCCESS, download_error);
    643   std::string content;
    644   EXPECT_EQ(output_file_path, kOutputFilePath);
    645   ASSERT_TRUE(base::ReadFileToString(output_file_path, &content));
    646   EXPECT_EQ("This is some test content.", content);
    647 }
    648 
    649 TEST_F(JobSchedulerTest, DownloadFileWimaxDisabled) {
    650   ConnectToWimax();
    651 
    652   // Disable fetching over cellular network.
    653   pref_service_->SetBoolean(prefs::kDisableDriveOverCellular, true);
    654 
    655   // Try to get a file in the background
    656   base::ScopedTempDir temp_dir;
    657   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    658 
    659   const base::FilePath kOutputFilePath =
    660       temp_dir.path().AppendASCII("whatever.txt");
    661   google_apis::GDataErrorCode download_error = google_apis::GDATA_OTHER_ERROR;
    662   base::FilePath output_file_path;
    663   scheduler_->DownloadFile(
    664       base::FilePath::FromUTF8Unsafe("drive/whatever.txt"),  // virtual path
    665       kDummyDownloadFileSize,
    666       kOutputFilePath,
    667       "file:2_file_resource_id",
    668       ClientContext(BACKGROUND),
    669       google_apis::test_util::CreateCopyResultCallback(
    670           &download_error, &output_file_path),
    671       google_apis::GetContentCallback());
    672   // Metadata should still work
    673   google_apis::GDataErrorCode metadata_error = google_apis::GDATA_OTHER_ERROR;
    674   scoped_ptr<google_apis::AboutResource> about_resource;
    675 
    676   // Try to get the metadata
    677   scheduler_->GetAboutResource(
    678       google_apis::test_util::CreateCopyResultCallback(
    679           &metadata_error, &about_resource));
    680   base::RunLoop().RunUntilIdle();
    681 
    682   // Check the metadata
    683   ASSERT_EQ(google_apis::HTTP_SUCCESS, metadata_error);
    684   ASSERT_TRUE(about_resource);
    685 
    686   // Check the download
    687   EXPECT_EQ(google_apis::GDATA_OTHER_ERROR, download_error);
    688 
    689   // Switch to a Wifi connection
    690   ConnectToWifi();
    691 
    692   base::RunLoop().RunUntilIdle();
    693 
    694   // Check the download again
    695   EXPECT_EQ(google_apis::HTTP_SUCCESS, download_error);
    696   std::string content;
    697   EXPECT_EQ(output_file_path, kOutputFilePath);
    698   ASSERT_TRUE(base::ReadFileToString(output_file_path, &content));
    699   EXPECT_EQ("This is some test content.", content);
    700 }
    701 
    702 TEST_F(JobSchedulerTest, DownloadFileCellularEnabled) {
    703   ConnectToCellular();
    704 
    705   // Enable fetching over cellular network.
    706   pref_service_->SetBoolean(prefs::kDisableDriveOverCellular, false);
    707 
    708   // Try to get a file in the background
    709   base::ScopedTempDir temp_dir;
    710   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    711 
    712   const base::FilePath kOutputFilePath =
    713       temp_dir.path().AppendASCII("whatever.txt");
    714   google_apis::GDataErrorCode download_error = google_apis::GDATA_OTHER_ERROR;
    715   base::FilePath output_file_path;
    716   scheduler_->DownloadFile(
    717       base::FilePath::FromUTF8Unsafe("drive/whatever.txt"),  // virtual path
    718       kDummyDownloadFileSize,
    719       kOutputFilePath,
    720       "file:2_file_resource_id",
    721       ClientContext(BACKGROUND),
    722       google_apis::test_util::CreateCopyResultCallback(
    723           &download_error, &output_file_path),
    724       google_apis::GetContentCallback());
    725   // Metadata should still work
    726   google_apis::GDataErrorCode metadata_error = google_apis::GDATA_OTHER_ERROR;
    727   scoped_ptr<google_apis::AboutResource> about_resource;
    728 
    729   // Try to get the metadata
    730   scheduler_->GetAboutResource(
    731       google_apis::test_util::CreateCopyResultCallback(
    732           &metadata_error, &about_resource));
    733   base::RunLoop().RunUntilIdle();
    734 
    735   // Check the metadata
    736   ASSERT_EQ(google_apis::HTTP_SUCCESS, metadata_error);
    737   ASSERT_TRUE(about_resource);
    738 
    739   // Check the download
    740   EXPECT_EQ(google_apis::HTTP_SUCCESS, download_error);
    741   std::string content;
    742   EXPECT_EQ(output_file_path, kOutputFilePath);
    743   ASSERT_TRUE(base::ReadFileToString(output_file_path, &content));
    744   EXPECT_EQ("This is some test content.", content);
    745 }
    746 
    747 TEST_F(JobSchedulerTest, DownloadFileWimaxEnabled) {
    748   ConnectToWimax();
    749 
    750   // Enable fetching over cellular network.
    751   pref_service_->SetBoolean(prefs::kDisableDriveOverCellular, false);
    752 
    753   // Try to get a file in the background
    754   base::ScopedTempDir temp_dir;
    755   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    756 
    757   const base::FilePath kOutputFilePath =
    758       temp_dir.path().AppendASCII("whatever.txt");
    759   google_apis::GDataErrorCode download_error = google_apis::GDATA_OTHER_ERROR;
    760   base::FilePath output_file_path;
    761   scheduler_->DownloadFile(
    762       base::FilePath::FromUTF8Unsafe("drive/whatever.txt"),  // virtual path
    763       kDummyDownloadFileSize,
    764       kOutputFilePath,
    765       "file:2_file_resource_id",
    766       ClientContext(BACKGROUND),
    767       google_apis::test_util::CreateCopyResultCallback(
    768           &download_error, &output_file_path),
    769       google_apis::GetContentCallback());
    770   // Metadata should still work
    771   google_apis::GDataErrorCode metadata_error = google_apis::GDATA_OTHER_ERROR;
    772   scoped_ptr<google_apis::AboutResource> about_resource;
    773 
    774   // Try to get the metadata
    775   scheduler_->GetAboutResource(
    776       google_apis::test_util::CreateCopyResultCallback(
    777           &metadata_error, &about_resource));
    778   base::RunLoop().RunUntilIdle();
    779 
    780   // Check the metadata
    781   ASSERT_EQ(google_apis::HTTP_SUCCESS, metadata_error);
    782   ASSERT_TRUE(about_resource);
    783 
    784   // Check the download
    785   EXPECT_EQ(google_apis::HTTP_SUCCESS, download_error);
    786   std::string content;
    787   EXPECT_EQ(output_file_path, kOutputFilePath);
    788   ASSERT_TRUE(base::ReadFileToString(output_file_path, &content));
    789   EXPECT_EQ("This is some test content.", content);
    790 }
    791 
    792 TEST_F(JobSchedulerTest, JobInfo) {
    793   JobListLogger logger;
    794   scheduler_->AddObserver(&logger);
    795 
    796   // Disable background upload/download.
    797   ConnectToWimax();
    798   pref_service_->SetBoolean(prefs::kDisableDriveOverCellular, true);
    799 
    800   base::ScopedTempDir temp_dir;
    801   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    802 
    803   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    804   scoped_ptr<google_apis::FileResource> entry;
    805   scoped_ptr<google_apis::AboutResource> about_resource;
    806   base::FilePath path;
    807 
    808   std::set<JobType> expected_types;
    809 
    810   // Add many jobs.
    811   expected_types.insert(TYPE_ADD_NEW_DIRECTORY);
    812   scheduler_->AddNewDirectory(
    813       fake_drive_service_->GetRootResourceId(),
    814       "New Directory",
    815       DriveServiceInterface::AddNewDirectoryOptions(),
    816       ClientContext(USER_INITIATED),
    817       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    818   expected_types.insert(TYPE_GET_ABOUT_RESOURCE);
    819   scheduler_->GetAboutResource(
    820       google_apis::test_util::CreateCopyResultCallback(
    821           &error, &about_resource));
    822   expected_types.insert(TYPE_RENAME_RESOURCE);
    823   scheduler_->RenameResource(
    824       "file:2_file_resource_id",
    825       "New Title",
    826       google_apis::test_util::CreateCopyResultCallback(&error));
    827   expected_types.insert(TYPE_DOWNLOAD_FILE);
    828   scheduler_->DownloadFile(
    829       base::FilePath::FromUTF8Unsafe("drive/whatever.txt"),  // virtual path
    830       kDummyDownloadFileSize,
    831       temp_dir.path().AppendASCII("whatever.txt"),
    832       "file:2_file_resource_id",
    833       ClientContext(BACKGROUND),
    834       google_apis::test_util::CreateCopyResultCallback(&error, &path),
    835       google_apis::GetContentCallback());
    836 
    837   // The number of jobs queued so far.
    838   EXPECT_EQ(4U, scheduler_->GetJobInfoList().size());
    839   EXPECT_TRUE(logger.Has(JobListLogger::ADDED, TYPE_ADD_NEW_DIRECTORY));
    840   EXPECT_TRUE(logger.Has(JobListLogger::ADDED, TYPE_GET_ABOUT_RESOURCE));
    841   EXPECT_TRUE(logger.Has(JobListLogger::ADDED, TYPE_RENAME_RESOURCE));
    842   EXPECT_TRUE(logger.Has(JobListLogger::ADDED, TYPE_DOWNLOAD_FILE));
    843   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_ADD_NEW_DIRECTORY));
    844   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_GET_ABOUT_RESOURCE));
    845   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_RENAME_RESOURCE));
    846   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_DOWNLOAD_FILE));
    847 
    848   // Add more jobs.
    849   expected_types.insert(TYPE_ADD_RESOURCE_TO_DIRECTORY);
    850   scheduler_->AddResourceToDirectory(
    851       "folder:1_folder_resource_id",
    852       "file:2_file_resource_id",
    853       google_apis::test_util::CreateCopyResultCallback(&error));
    854   expected_types.insert(TYPE_COPY_RESOURCE);
    855   scheduler_->CopyResource(
    856       "document:5_document_resource_id",
    857       fake_drive_service_->GetRootResourceId(),
    858       "New Document",
    859       base::Time(),  // last_modified
    860       google_apis::test_util::CreateCopyResultCallback(&error, &entry));
    861 
    862   // 6 jobs in total were queued.
    863   std::vector<JobInfo> jobs = scheduler_->GetJobInfoList();
    864   EXPECT_EQ(6U, jobs.size());
    865   std::set<JobType> actual_types;
    866   std::set<JobID> job_ids;
    867   for (size_t i = 0; i < jobs.size(); ++i) {
    868     actual_types.insert(jobs[i].job_type);
    869     job_ids.insert(jobs[i].job_id);
    870   }
    871   EXPECT_EQ(expected_types, actual_types);
    872   EXPECT_EQ(6U, job_ids.size()) << "All job IDs must be unique";
    873   EXPECT_TRUE(logger.Has(JobListLogger::ADDED, TYPE_ADD_RESOURCE_TO_DIRECTORY));
    874   EXPECT_TRUE(logger.Has(JobListLogger::ADDED, TYPE_COPY_RESOURCE));
    875   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_ADD_RESOURCE_TO_DIRECTORY));
    876   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_COPY_RESOURCE));
    877 
    878   // Run the jobs.
    879   base::RunLoop().RunUntilIdle();
    880 
    881   // All jobs except the BACKGROUND job should have started running (UPDATED)
    882   // and then finished (DONE).
    883   jobs = scheduler_->GetJobInfoList();
    884   ASSERT_EQ(1U, jobs.size());
    885   EXPECT_EQ(TYPE_DOWNLOAD_FILE, jobs[0].job_type);
    886 
    887   EXPECT_TRUE(logger.Has(JobListLogger::UPDATED, TYPE_ADD_NEW_DIRECTORY));
    888   EXPECT_TRUE(logger.Has(JobListLogger::UPDATED, TYPE_GET_ABOUT_RESOURCE));
    889   EXPECT_TRUE(logger.Has(JobListLogger::UPDATED, TYPE_RENAME_RESOURCE));
    890   EXPECT_TRUE(logger.Has(JobListLogger::UPDATED,
    891                          TYPE_ADD_RESOURCE_TO_DIRECTORY));
    892   EXPECT_TRUE(logger.Has(JobListLogger::UPDATED, TYPE_COPY_RESOURCE));
    893   EXPECT_FALSE(logger.Has(JobListLogger::UPDATED, TYPE_DOWNLOAD_FILE));
    894 
    895   EXPECT_TRUE(logger.Has(JobListLogger::DONE, TYPE_ADD_NEW_DIRECTORY));
    896   EXPECT_TRUE(logger.Has(JobListLogger::DONE, TYPE_GET_ABOUT_RESOURCE));
    897   EXPECT_TRUE(logger.Has(JobListLogger::DONE, TYPE_RENAME_RESOURCE));
    898   EXPECT_TRUE(logger.Has(JobListLogger::DONE, TYPE_ADD_RESOURCE_TO_DIRECTORY));
    899   EXPECT_TRUE(logger.Has(JobListLogger::DONE, TYPE_COPY_RESOURCE));
    900   EXPECT_FALSE(logger.Has(JobListLogger::DONE, TYPE_DOWNLOAD_FILE));
    901 
    902   // Run the background downloading job as well.
    903   ConnectToWifi();
    904   base::RunLoop().RunUntilIdle();
    905 
    906   // All jobs should have finished.
    907   EXPECT_EQ(0U, scheduler_->GetJobInfoList().size());
    908   EXPECT_TRUE(logger.Has(JobListLogger::UPDATED, TYPE_DOWNLOAD_FILE));
    909   EXPECT_TRUE(logger.Has(JobListLogger::DONE, TYPE_DOWNLOAD_FILE));
    910 }
    911 
    912 TEST_F(JobSchedulerTest, JobInfoProgress) {
    913   JobListLogger logger;
    914   scheduler_->AddObserver(&logger);
    915 
    916   ConnectToWifi();
    917 
    918   base::ScopedTempDir temp_dir;
    919   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    920 
    921   google_apis::GDataErrorCode error = google_apis::GDATA_OTHER_ERROR;
    922   base::FilePath path;
    923 
    924   // Download job.
    925   scheduler_->DownloadFile(
    926       base::FilePath::FromUTF8Unsafe("drive/whatever.txt"),  // virtual path
    927       kDummyDownloadFileSize,
    928       temp_dir.path().AppendASCII("whatever.txt"),
    929       "file:2_file_resource_id",
    930       ClientContext(BACKGROUND),
    931       google_apis::test_util::CreateCopyResultCallback(&error, &path),
    932       google_apis::GetContentCallback());
    933   base::RunLoop().RunUntilIdle();
    934 
    935   std::vector<int64> download_progress;
    936   logger.GetProgressInfo(TYPE_DOWNLOAD_FILE, &download_progress);
    937   ASSERT_TRUE(!download_progress.empty());
    938   EXPECT_TRUE(base::STLIsSorted(download_progress));
    939   EXPECT_GE(download_progress.front(), 0);
    940   EXPECT_LE(download_progress.back(), 26);
    941 
    942   // Upload job.
    943   path = temp_dir.path().AppendASCII("new_file.txt");
    944   ASSERT_TRUE(google_apis::test_util::WriteStringToFile(path, "Hello"));
    945   google_apis::GDataErrorCode upload_error =
    946       google_apis::GDATA_OTHER_ERROR;
    947   scoped_ptr<google_apis::FileResource> entry;
    948 
    949   scheduler_->UploadNewFile(
    950       fake_drive_service_->GetRootResourceId(),
    951       base::FilePath::FromUTF8Unsafe("drive/new_file.txt"),
    952       path,
    953       "dummy title",
    954       "plain/plain",
    955       DriveUploader::UploadNewFileOptions(),
    956       ClientContext(BACKGROUND),
    957       google_apis::test_util::CreateCopyResultCallback(&upload_error, &entry));
    958   base::RunLoop().RunUntilIdle();
    959 
    960   std::vector<int64> upload_progress;
    961   logger.GetProgressInfo(TYPE_UPLOAD_NEW_FILE, &upload_progress);
    962   ASSERT_TRUE(!upload_progress.empty());
    963   EXPECT_TRUE(base::STLIsSorted(upload_progress));
    964   EXPECT_GE(upload_progress.front(), 0);
    965   EXPECT_LE(upload_progress.back(), 13);
    966 }
    967 
    968 TEST_F(JobSchedulerTest, CancelPendingJob) {
    969   base::ScopedTempDir temp_dir;
    970   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
    971   base::FilePath upload_path = temp_dir.path().AppendASCII("new_file.txt");
    972   ASSERT_TRUE(google_apis::test_util::WriteStringToFile(upload_path, "Hello"));
    973 
    974   // To create a pending job for testing, set the mode to cellular connection
    975   // and issue BACKGROUND jobs.
    976   ConnectToCellular();
    977   pref_service_->SetBoolean(prefs::kDisableDriveOverCellular, true);
    978 
    979   // Start the first job and record its job ID.
    980   google_apis::GDataErrorCode error1 = google_apis::GDATA_OTHER_ERROR;
    981   scoped_ptr<google_apis::FileResource> entry;
    982   scheduler_->UploadNewFile(
    983       fake_drive_service_->GetRootResourceId(),
    984       base::FilePath::FromUTF8Unsafe("dummy/path"),
    985       upload_path,
    986       "dummy title 1",
    987       "text/plain",
    988       DriveUploader::UploadNewFileOptions(),
    989       ClientContext(BACKGROUND),
    990       google_apis::test_util::CreateCopyResultCallback(&error1, &entry));
    991 
    992   const std::vector<JobInfo>& jobs = scheduler_->GetJobInfoList();
    993   ASSERT_EQ(1u, jobs.size());
    994   ASSERT_EQ(STATE_NONE, jobs[0].state);  // Not started yet.
    995   JobID first_job_id = jobs[0].job_id;
    996 
    997   // Start the second job.
    998   google_apis::GDataErrorCode error2 = google_apis::GDATA_OTHER_ERROR;
    999   scheduler_->UploadNewFile(
   1000       fake_drive_service_->GetRootResourceId(),
   1001       base::FilePath::FromUTF8Unsafe("dummy/path"),
   1002       upload_path,
   1003       "dummy title 2",
   1004       "text/plain",
   1005       DriveUploader::UploadNewFileOptions(),
   1006       ClientContext(BACKGROUND),
   1007       google_apis::test_util::CreateCopyResultCallback(&error2, &entry));
   1008 
   1009   // Cancel the first one.
   1010   scheduler_->CancelJob(first_job_id);
   1011 
   1012   // Only the first job should be cancelled.
   1013   ConnectToWifi();
   1014   base::RunLoop().RunUntilIdle();
   1015   EXPECT_EQ(google_apis::GDATA_CANCELLED, error1);
   1016   EXPECT_EQ(google_apis::HTTP_SUCCESS, error2);
   1017   EXPECT_TRUE(scheduler_->GetJobInfoList().empty());
   1018 }
   1019 
   1020 TEST_F(JobSchedulerTest, CancelRunningJob) {
   1021   ConnectToWifi();
   1022 
   1023   base::ScopedTempDir temp_dir;
   1024   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
   1025   base::FilePath upload_path = temp_dir.path().AppendASCII("new_file.txt");
   1026   ASSERT_TRUE(google_apis::test_util::WriteStringToFile(upload_path, "Hello"));
   1027 
   1028   // Run as a cancelable task.
   1029   fake_drive_service_->set_upload_new_file_cancelable(true);
   1030   google_apis::GDataErrorCode error1 = google_apis::GDATA_OTHER_ERROR;
   1031   scoped_ptr<google_apis::FileResource> entry;
   1032   scheduler_->UploadNewFile(
   1033       fake_drive_service_->GetRootResourceId(),
   1034       base::FilePath::FromUTF8Unsafe("dummy/path"),
   1035       upload_path,
   1036       "dummy title 1",
   1037       "text/plain",
   1038       DriveUploader::UploadNewFileOptions(),
   1039       ClientContext(USER_INITIATED),
   1040       google_apis::test_util::CreateCopyResultCallback(&error1, &entry));
   1041 
   1042   const std::vector<JobInfo>& jobs = scheduler_->GetJobInfoList();
   1043   ASSERT_EQ(1u, jobs.size());
   1044   ASSERT_EQ(STATE_RUNNING, jobs[0].state);  // It's running.
   1045   JobID first_job_id = jobs[0].job_id;
   1046 
   1047   // Start the second job normally.
   1048   fake_drive_service_->set_upload_new_file_cancelable(false);
   1049   google_apis::GDataErrorCode error2 = google_apis::GDATA_OTHER_ERROR;
   1050   scheduler_->UploadNewFile(
   1051       fake_drive_service_->GetRootResourceId(),
   1052       base::FilePath::FromUTF8Unsafe("dummy/path"),
   1053       upload_path,
   1054       "dummy title 2",
   1055       "text/plain",
   1056       DriveUploader::UploadNewFileOptions(),
   1057       ClientContext(USER_INITIATED),
   1058       google_apis::test_util::CreateCopyResultCallback(&error2, &entry));
   1059 
   1060   // Cancel the first one.
   1061   scheduler_->CancelJob(first_job_id);
   1062 
   1063   // Only the first job should be cancelled.
   1064   base::RunLoop().RunUntilIdle();
   1065   EXPECT_EQ(google_apis::GDATA_CANCELLED, error1);
   1066   EXPECT_EQ(google_apis::HTTP_SUCCESS, error2);
   1067   EXPECT_TRUE(scheduler_->GetJobInfoList().empty());
   1068 }
   1069 
   1070 }  // namespace drive
   1071