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