| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/drive/sync_client.h" |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "chrome/browser/chromeos/drive/drive.pb.h" |
| #include "chrome/browser/chromeos/drive/file_cache.h" |
| #include "chrome/browser/chromeos/drive/file_system/download_operation.h" |
| #include "chrome/browser/chromeos/drive/file_system/update_operation.h" |
| #include "chrome/browser/chromeos/drive/file_system_util.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| using content::BrowserThread; |
| |
| namespace drive { |
| namespace internal { |
| |
| namespace { |
| |
| // The delay constant is used to delay processing a sync task. We should not |
| // process SyncTasks immediately for the following reasons: |
| // |
| // 1) For fetching, the user may accidentally click on "Make available |
| // offline" checkbox on a file, and immediately cancel it in a second. |
| // It's a waste to fetch the file in this case. |
| // |
| // 2) For uploading, file writing via HTML5 file system API is performed in |
| // two steps: 1) truncate a file to 0 bytes, 2) write contents. We |
| // shouldn't start uploading right after the step 1). Besides, the user |
| // may edit the same file repeatedly in a short period of time. |
| // |
| // TODO(satorux): We should find a way to handle the upload case more nicely, |
| // and shorten the delay. crbug.com/134774 |
| const int kDelaySeconds = 5; |
| |
| // The delay constant is used to delay retrying a sync task on server errors. |
| const int kLongDelaySeconds = 600; |
| |
| // Appends |resource_id| to |to_fetch| if the file is pinned but not fetched |
| // (not present locally), or to |to_upload| if the file is dirty but not |
| // uploaded. |
| void CollectBacklog(std::vector<std::string>* to_fetch, |
| std::vector<std::string>* to_upload, |
| const std::string& resource_id, |
| const FileCacheEntry& cache_entry) { |
| DCHECK(to_fetch); |
| DCHECK(to_upload); |
| |
| if (cache_entry.is_pinned() && !cache_entry.is_present()) |
| to_fetch->push_back(resource_id); |
| |
| if (cache_entry.is_dirty()) |
| to_upload->push_back(resource_id); |
| } |
| |
| } // namespace |
| |
| SyncClient::SyncClient(base::SequencedTaskRunner* blocking_task_runner, |
| file_system::OperationObserver* observer, |
| JobScheduler* scheduler, |
| ResourceMetadata* metadata, |
| FileCache* cache, |
| const base::FilePath& temporary_file_directory) |
| : metadata_(metadata), |
| cache_(cache), |
| download_operation_(new file_system::DownloadOperation( |
| blocking_task_runner, |
| observer, |
| scheduler, |
| metadata, |
| cache, |
| temporary_file_directory)), |
| update_operation_(new file_system::UpdateOperation(blocking_task_runner, |
| observer, |
| scheduler, |
| metadata, |
| cache)), |
| delay_(base::TimeDelta::FromSeconds(kDelaySeconds)), |
| long_delay_(base::TimeDelta::FromSeconds(kLongDelaySeconds)), |
| weak_ptr_factory_(this) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| } |
| |
| SyncClient::~SyncClient() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| } |
| |
| void SyncClient::StartProcessingBacklog() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| std::vector<std::string>* to_fetch = new std::vector<std::string>; |
| std::vector<std::string>* to_upload = new std::vector<std::string>; |
| cache_->IterateOnUIThread(base::Bind(&CollectBacklog, to_fetch, to_upload), |
| base::Bind(&SyncClient::OnGetResourceIdsOfBacklog, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Owned(to_fetch), |
| base::Owned(to_upload))); |
| } |
| |
| void SyncClient::StartCheckingExistingPinnedFiles() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| cache_->IterateOnUIThread( |
| base::Bind(&SyncClient::OnGetResourceIdOfExistingPinnedFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&base::DoNothing)); |
| } |
| |
| void SyncClient::AddFetchTask(const std::string& resource_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| AddTaskToQueue(FETCH, resource_id, delay_); |
| } |
| |
| void SyncClient::RemoveFetchTask(const std::string& resource_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // TODO(kinaba): Cancel tasks in JobScheduler as well. crbug.com/248856 |
| pending_fetch_list_.erase(resource_id); |
| } |
| |
| void SyncClient::AddUploadTask(const std::string& resource_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| AddTaskToQueue(UPLOAD, resource_id, delay_); |
| } |
| |
| void SyncClient::AddTaskToQueue(SyncType type, |
| const std::string& resource_id, |
| const base::TimeDelta& delay) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If the same task is already queued, ignore this task. |
| switch (type) { |
| case FETCH: |
| if (fetch_list_.find(resource_id) == fetch_list_.end()) { |
| fetch_list_.insert(resource_id); |
| pending_fetch_list_.insert(resource_id); |
| } else { |
| return; |
| } |
| break; |
| case UPLOAD: |
| case UPLOAD_NO_CONTENT_CHECK: |
| if (upload_list_.find(resource_id) == upload_list_.end()) { |
| upload_list_.insert(resource_id); |
| } else { |
| return; |
| } |
| break; |
| } |
| |
| base::MessageLoopProxy::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&SyncClient::StartTask, |
| weak_ptr_factory_.GetWeakPtr(), |
| type, |
| resource_id), |
| delay); |
| } |
| |
| void SyncClient::StartTask(SyncType type, const std::string& resource_id) { |
| switch (type) { |
| case FETCH: |
| // Check if the resource has been removed from the start list. |
| if (pending_fetch_list_.find(resource_id) != pending_fetch_list_.end()) { |
| DVLOG(1) << "Fetching " << resource_id; |
| pending_fetch_list_.erase(resource_id); |
| |
| download_operation_->EnsureFileDownloadedByResourceId( |
| resource_id, |
| ClientContext(BACKGROUND), |
| GetFileContentInitializedCallback(), |
| google_apis::GetContentCallback(), |
| base::Bind(&SyncClient::OnFetchFileComplete, |
| weak_ptr_factory_.GetWeakPtr(), |
| resource_id)); |
| } else { |
| // Cancel the task. |
| fetch_list_.erase(resource_id); |
| } |
| break; |
| case UPLOAD: |
| case UPLOAD_NO_CONTENT_CHECK: |
| DVLOG(1) << "Uploading " << resource_id; |
| update_operation_->UpdateFileByResourceId( |
| resource_id, |
| ClientContext(BACKGROUND), |
| type == UPLOAD ? file_system::UpdateOperation::RUN_CONTENT_CHECK |
| : file_system::UpdateOperation::NO_CONTENT_CHECK, |
| base::Bind(&SyncClient::OnUploadFileComplete, |
| weak_ptr_factory_.GetWeakPtr(), |
| resource_id)); |
| break; |
| } |
| } |
| |
| void SyncClient::OnGetResourceIdsOfBacklog( |
| const std::vector<std::string>* to_fetch, |
| const std::vector<std::string>* to_upload) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Give priority to upload tasks over fetch tasks, so that dirty files are |
| // uploaded as soon as possible. |
| for (size_t i = 0; i < to_upload->size(); ++i) { |
| const std::string& resource_id = (*to_upload)[i]; |
| DVLOG(1) << "Queuing to upload: " << resource_id; |
| AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, resource_id, delay_); |
| } |
| |
| for (size_t i = 0; i < to_fetch->size(); ++i) { |
| const std::string& resource_id = (*to_fetch)[i]; |
| DVLOG(1) << "Queuing to fetch: " << resource_id; |
| AddTaskToQueue(FETCH, resource_id, delay_); |
| } |
| } |
| |
| void SyncClient::OnGetResourceIdOfExistingPinnedFile( |
| const std::string& resource_id, |
| const FileCacheEntry& cache_entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (cache_entry.is_pinned() && cache_entry.is_present()) { |
| metadata_->GetResourceEntryByIdOnUIThread( |
| resource_id, |
| base::Bind(&SyncClient::OnGetResourceEntryById, |
| weak_ptr_factory_.GetWeakPtr(), |
| resource_id, |
| cache_entry)); |
| } |
| } |
| |
| void SyncClient::OnGetResourceEntryById( |
| const std::string& resource_id, |
| const FileCacheEntry& cache_entry, |
| FileError error, |
| scoped_ptr<ResourceEntry> entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (entry.get() && !entry->has_file_specific_info()) |
| error = FILE_ERROR_NOT_FOUND; |
| |
| if (error != FILE_ERROR_OK) { |
| LOG(WARNING) << "Entry not found: " << resource_id; |
| return; |
| } |
| |
| // If MD5s don't match, it indicates the local cache file is stale, unless |
| // the file is dirty (the MD5 is "local"). We should never re-fetch the |
| // file when we have a locally modified version. |
| if (entry->file_specific_info().md5() != cache_entry.md5() && |
| !cache_entry.is_dirty()) { |
| cache_->RemoveOnUIThread(resource_id, |
| base::Bind(&SyncClient::OnRemove, |
| weak_ptr_factory_.GetWeakPtr(), |
| resource_id)); |
| } |
| } |
| |
| void SyncClient::OnRemove(const std::string& resource_id, |
| FileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != FILE_ERROR_OK) { |
| LOG(WARNING) << "Failed to remove cache entry: " << resource_id; |
| return; |
| } |
| |
| // Before fetching, we should pin this file again, so that the fetched file |
| // is downloaded properly to the persistent directory and marked pinned. |
| cache_->PinOnUIThread(resource_id, |
| base::Bind(&SyncClient::OnPinned, |
| weak_ptr_factory_.GetWeakPtr(), |
| resource_id)); |
| } |
| |
| void SyncClient::OnPinned(const std::string& resource_id, |
| FileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != FILE_ERROR_OK) { |
| LOG(WARNING) << "Failed to pin cache entry: " << resource_id; |
| return; |
| } |
| |
| // Finally, adding to the queue. |
| AddTaskToQueue(FETCH, resource_id, delay_); |
| } |
| |
| void SyncClient::OnFetchFileComplete(const std::string& resource_id, |
| FileError error, |
| const base::FilePath& local_path, |
| scoped_ptr<ResourceEntry> entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| fetch_list_.erase(resource_id); |
| |
| if (error == FILE_ERROR_OK) { |
| DVLOG(1) << "Fetched " << resource_id << ": " |
| << local_path.value(); |
| } else { |
| switch (error) { |
| case FILE_ERROR_ABORT: |
| // If user cancels download, unpin the file so that we do not sync the |
| // file again. |
| cache_->UnpinOnUIThread(resource_id, |
| base::Bind(&util::EmptyFileOperationCallback)); |
| break; |
| case FILE_ERROR_NO_CONNECTION: |
| // Re-queue the task so that we'll retry once the connection is back. |
| AddTaskToQueue(FETCH, resource_id, delay_); |
| break; |
| case FILE_ERROR_SERVICE_UNAVAILABLE: |
| // Re-queue the task so that we'll retry once the service is back. |
| AddTaskToQueue(FETCH, resource_id, long_delay_); |
| break; |
| default: |
| LOG(WARNING) << "Failed to fetch " << resource_id |
| << ": " << FileErrorToString(error); |
| } |
| } |
| } |
| |
| void SyncClient::OnUploadFileComplete(const std::string& resource_id, |
| FileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| upload_list_.erase(resource_id); |
| |
| if (error == FILE_ERROR_OK) { |
| DVLOG(1) << "Uploaded " << resource_id; |
| } else { |
| switch (error) { |
| case FILE_ERROR_NO_CONNECTION: |
| // Re-queue the task so that we'll retry once the connection is back. |
| AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, resource_id, delay_); |
| break; |
| case FILE_ERROR_SERVICE_UNAVAILABLE: |
| // Re-queue the task so that we'll retry once the service is back. |
| AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, resource_id, long_delay_); |
| break; |
| default: |
| LOG(WARNING) << "Failed to upload " << resource_id << ": " |
| << FileErrorToString(error); |
| } |
| } |
| } |
| |
| } // namespace internal |
| } // namespace drive |