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/sync_client.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/bind.h"
     10 #include "base/message_loop/message_loop_proxy.h"
     11 #include "chrome/browser/chromeos/drive/drive.pb.h"
     12 #include "chrome/browser/chromeos/drive/file_cache.h"
     13 #include "chrome/browser/chromeos/drive/file_system/download_operation.h"
     14 #include "chrome/browser/chromeos/drive/file_system/update_operation.h"
     15 #include "chrome/browser/chromeos/drive/file_system_util.h"
     16 #include "content/public/browser/browser_thread.h"
     17 
     18 using content::BrowserThread;
     19 
     20 namespace drive {
     21 namespace internal {
     22 
     23 namespace {
     24 
     25 // The delay constant is used to delay processing a sync task. We should not
     26 // process SyncTasks immediately for the following reasons:
     27 //
     28 // 1) For fetching, the user may accidentally click on "Make available
     29 //    offline" checkbox on a file, and immediately cancel it in a second.
     30 //    It's a waste to fetch the file in this case.
     31 //
     32 // 2) For uploading, file writing via HTML5 file system API is performed in
     33 //    two steps: 1) truncate a file to 0 bytes, 2) write contents. We
     34 //    shouldn't start uploading right after the step 1). Besides, the user
     35 //    may edit the same file repeatedly in a short period of time.
     36 //
     37 // TODO(satorux): We should find a way to handle the upload case more nicely,
     38 // and shorten the delay. crbug.com/134774
     39 const int kDelaySeconds = 5;
     40 
     41 // The delay constant is used to delay retrying a sync task on server errors.
     42 const int kLongDelaySeconds = 600;
     43 
     44 // Appends |resource_id| to |to_fetch| if the file is pinned but not fetched
     45 // (not present locally), or to |to_upload| if the file is dirty but not
     46 // uploaded.
     47 void CollectBacklog(std::vector<std::string>* to_fetch,
     48                     std::vector<std::string>* to_upload,
     49                     const std::string& resource_id,
     50                     const FileCacheEntry& cache_entry) {
     51   DCHECK(to_fetch);
     52   DCHECK(to_upload);
     53 
     54   if (cache_entry.is_pinned() && !cache_entry.is_present())
     55     to_fetch->push_back(resource_id);
     56 
     57   if (cache_entry.is_dirty())
     58     to_upload->push_back(resource_id);
     59 }
     60 
     61 }  // namespace
     62 
     63 SyncClient::SyncClient(base::SequencedTaskRunner* blocking_task_runner,
     64                        file_system::OperationObserver* observer,
     65                        JobScheduler* scheduler,
     66                        ResourceMetadata* metadata,
     67                        FileCache* cache,
     68                        const base::FilePath& temporary_file_directory)
     69     : metadata_(metadata),
     70       cache_(cache),
     71       download_operation_(new file_system::DownloadOperation(
     72           blocking_task_runner,
     73           observer,
     74           scheduler,
     75           metadata,
     76           cache,
     77           temporary_file_directory)),
     78       update_operation_(new file_system::UpdateOperation(blocking_task_runner,
     79                                                          observer,
     80                                                          scheduler,
     81                                                          metadata,
     82                                                          cache)),
     83       delay_(base::TimeDelta::FromSeconds(kDelaySeconds)),
     84       long_delay_(base::TimeDelta::FromSeconds(kLongDelaySeconds)),
     85       weak_ptr_factory_(this) {
     86   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     87 }
     88 
     89 SyncClient::~SyncClient() {
     90   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     91 }
     92 
     93 void SyncClient::StartProcessingBacklog() {
     94   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     95 
     96   std::vector<std::string>* to_fetch = new std::vector<std::string>;
     97   std::vector<std::string>* to_upload = new std::vector<std::string>;
     98   cache_->IterateOnUIThread(base::Bind(&CollectBacklog, to_fetch, to_upload),
     99                             base::Bind(&SyncClient::OnGetResourceIdsOfBacklog,
    100                                        weak_ptr_factory_.GetWeakPtr(),
    101                                        base::Owned(to_fetch),
    102                                        base::Owned(to_upload)));
    103 }
    104 
    105 void SyncClient::StartCheckingExistingPinnedFiles() {
    106   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    107 
    108   cache_->IterateOnUIThread(
    109       base::Bind(&SyncClient::OnGetResourceIdOfExistingPinnedFile,
    110                  weak_ptr_factory_.GetWeakPtr()),
    111       base::Bind(&base::DoNothing));
    112 }
    113 
    114 void SyncClient::AddFetchTask(const std::string& resource_id) {
    115   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    116 
    117   AddTaskToQueue(FETCH, resource_id, delay_);
    118 }
    119 
    120 void SyncClient::RemoveFetchTask(const std::string& resource_id) {
    121   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    122 
    123   // TODO(kinaba): Cancel tasks in JobScheduler as well. crbug.com/248856
    124   pending_fetch_list_.erase(resource_id);
    125 }
    126 
    127 void SyncClient::AddUploadTask(const std::string& resource_id) {
    128   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    129 
    130   AddTaskToQueue(UPLOAD, resource_id, delay_);
    131 }
    132 
    133 void SyncClient::AddTaskToQueue(SyncType type,
    134                                 const std::string& resource_id,
    135                                 const base::TimeDelta& delay) {
    136   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    137 
    138   // If the same task is already queued, ignore this task.
    139   switch (type) {
    140     case FETCH:
    141       if (fetch_list_.find(resource_id) == fetch_list_.end()) {
    142         fetch_list_.insert(resource_id);
    143         pending_fetch_list_.insert(resource_id);
    144       } else {
    145         return;
    146       }
    147       break;
    148     case UPLOAD:
    149     case UPLOAD_NO_CONTENT_CHECK:
    150       if (upload_list_.find(resource_id) == upload_list_.end()) {
    151         upload_list_.insert(resource_id);
    152       } else {
    153         return;
    154       }
    155       break;
    156   }
    157 
    158   base::MessageLoopProxy::current()->PostDelayedTask(
    159       FROM_HERE,
    160       base::Bind(&SyncClient::StartTask,
    161                  weak_ptr_factory_.GetWeakPtr(),
    162                  type,
    163                  resource_id),
    164       delay);
    165 }
    166 
    167 void SyncClient::StartTask(SyncType type, const std::string& resource_id) {
    168   switch (type) {
    169     case FETCH:
    170       // Check if the resource has been removed from the start list.
    171       if (pending_fetch_list_.find(resource_id) != pending_fetch_list_.end()) {
    172         DVLOG(1) << "Fetching " << resource_id;
    173         pending_fetch_list_.erase(resource_id);
    174 
    175         download_operation_->EnsureFileDownloadedByResourceId(
    176             resource_id,
    177             ClientContext(BACKGROUND),
    178             GetFileContentInitializedCallback(),
    179             google_apis::GetContentCallback(),
    180             base::Bind(&SyncClient::OnFetchFileComplete,
    181                        weak_ptr_factory_.GetWeakPtr(),
    182                        resource_id));
    183       } else {
    184         // Cancel the task.
    185         fetch_list_.erase(resource_id);
    186       }
    187       break;
    188     case UPLOAD:
    189     case UPLOAD_NO_CONTENT_CHECK:
    190       DVLOG(1) << "Uploading " << resource_id;
    191       update_operation_->UpdateFileByResourceId(
    192           resource_id,
    193           ClientContext(BACKGROUND),
    194           type == UPLOAD ? file_system::UpdateOperation::RUN_CONTENT_CHECK
    195                          : file_system::UpdateOperation::NO_CONTENT_CHECK,
    196           base::Bind(&SyncClient::OnUploadFileComplete,
    197                      weak_ptr_factory_.GetWeakPtr(),
    198                      resource_id));
    199       break;
    200   }
    201 }
    202 
    203 void SyncClient::OnGetResourceIdsOfBacklog(
    204     const std::vector<std::string>* to_fetch,
    205     const std::vector<std::string>* to_upload) {
    206   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    207 
    208   // Give priority to upload tasks over fetch tasks, so that dirty files are
    209   // uploaded as soon as possible.
    210   for (size_t i = 0; i < to_upload->size(); ++i) {
    211     const std::string& resource_id = (*to_upload)[i];
    212     DVLOG(1) << "Queuing to upload: " << resource_id;
    213     AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, resource_id, delay_);
    214   }
    215 
    216   for (size_t i = 0; i < to_fetch->size(); ++i) {
    217     const std::string& resource_id = (*to_fetch)[i];
    218     DVLOG(1) << "Queuing to fetch: " << resource_id;
    219     AddTaskToQueue(FETCH, resource_id, delay_);
    220   }
    221 }
    222 
    223 void SyncClient::OnGetResourceIdOfExistingPinnedFile(
    224     const std::string& resource_id,
    225     const FileCacheEntry& cache_entry) {
    226   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    227 
    228   if (cache_entry.is_pinned() && cache_entry.is_present()) {
    229     metadata_->GetResourceEntryByIdOnUIThread(
    230         resource_id,
    231         base::Bind(&SyncClient::OnGetResourceEntryById,
    232                    weak_ptr_factory_.GetWeakPtr(),
    233                    resource_id,
    234                    cache_entry));
    235   }
    236 }
    237 
    238 void SyncClient::OnGetResourceEntryById(
    239     const std::string& resource_id,
    240     const FileCacheEntry& cache_entry,
    241     FileError error,
    242     scoped_ptr<ResourceEntry> entry) {
    243   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    244 
    245   if (entry.get() && !entry->has_file_specific_info())
    246     error = FILE_ERROR_NOT_FOUND;
    247 
    248   if (error != FILE_ERROR_OK) {
    249     LOG(WARNING) << "Entry not found: " << resource_id;
    250     return;
    251   }
    252 
    253   // If MD5s don't match, it indicates the local cache file is stale, unless
    254   // the file is dirty (the MD5 is "local"). We should never re-fetch the
    255   // file when we have a locally modified version.
    256   if (entry->file_specific_info().md5() != cache_entry.md5() &&
    257       !cache_entry.is_dirty()) {
    258     cache_->RemoveOnUIThread(resource_id,
    259                              base::Bind(&SyncClient::OnRemove,
    260                                         weak_ptr_factory_.GetWeakPtr(),
    261                                         resource_id));
    262   }
    263 }
    264 
    265 void SyncClient::OnRemove(const std::string& resource_id,
    266                           FileError error) {
    267   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    268 
    269   if (error != FILE_ERROR_OK) {
    270     LOG(WARNING) << "Failed to remove cache entry: " << resource_id;
    271     return;
    272   }
    273 
    274   // Before fetching, we should pin this file again, so that the fetched file
    275   // is downloaded properly to the persistent directory and marked pinned.
    276   cache_->PinOnUIThread(resource_id,
    277                         base::Bind(&SyncClient::OnPinned,
    278                                    weak_ptr_factory_.GetWeakPtr(),
    279                                    resource_id));
    280 }
    281 
    282 void SyncClient::OnPinned(const std::string& resource_id,
    283                           FileError error) {
    284   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    285 
    286   if (error != FILE_ERROR_OK) {
    287     LOG(WARNING) << "Failed to pin cache entry: " << resource_id;
    288     return;
    289   }
    290 
    291   // Finally, adding to the queue.
    292   AddTaskToQueue(FETCH, resource_id, delay_);
    293 }
    294 
    295 void SyncClient::OnFetchFileComplete(const std::string& resource_id,
    296                                      FileError error,
    297                                      const base::FilePath& local_path,
    298                                      scoped_ptr<ResourceEntry> entry) {
    299   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    300 
    301   fetch_list_.erase(resource_id);
    302 
    303   if (error == FILE_ERROR_OK) {
    304     DVLOG(1) << "Fetched " << resource_id << ": "
    305              << local_path.value();
    306   } else {
    307     switch (error) {
    308       case FILE_ERROR_ABORT:
    309         // If user cancels download, unpin the file so that we do not sync the
    310         // file again.
    311         cache_->UnpinOnUIThread(resource_id,
    312                                 base::Bind(&util::EmptyFileOperationCallback));
    313         break;
    314       case FILE_ERROR_NO_CONNECTION:
    315         // Re-queue the task so that we'll retry once the connection is back.
    316         AddTaskToQueue(FETCH, resource_id, delay_);
    317         break;
    318       case FILE_ERROR_SERVICE_UNAVAILABLE:
    319         // Re-queue the task so that we'll retry once the service is back.
    320         AddTaskToQueue(FETCH, resource_id, long_delay_);
    321         break;
    322       default:
    323         LOG(WARNING) << "Failed to fetch " << resource_id
    324                      << ": " << FileErrorToString(error);
    325     }
    326   }
    327 }
    328 
    329 void SyncClient::OnUploadFileComplete(const std::string& resource_id,
    330                                       FileError error) {
    331   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    332 
    333   upload_list_.erase(resource_id);
    334 
    335   if (error == FILE_ERROR_OK) {
    336     DVLOG(1) << "Uploaded " << resource_id;
    337   } else {
    338     switch (error) {
    339       case FILE_ERROR_NO_CONNECTION:
    340         // Re-queue the task so that we'll retry once the connection is back.
    341         AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, resource_id, delay_);
    342         break;
    343       case FILE_ERROR_SERVICE_UNAVAILABLE:
    344         // Re-queue the task so that we'll retry once the service is back.
    345         AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, resource_id, long_delay_);
    346         break;
    347       default:
    348         LOG(WARNING) << "Failed to upload " << resource_id << ": "
    349                      << FileErrorToString(error);
    350     }
    351   }
    352 }
    353 
    354 }  // namespace internal
    355 }  // namespace drive
    356