entry_update_performer.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright 2013 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/entry_update_performer.h" 6 7#include "base/callback_helpers.h" 8#include "base/files/file_util.h" 9#include "chrome/browser/chromeos/drive/change_list_loader.h" 10#include "chrome/browser/chromeos/drive/drive.pb.h" 11#include "chrome/browser/chromeos/drive/file_cache.h" 12#include "chrome/browser/chromeos/drive/file_change.h" 13#include "chrome/browser/chromeos/drive/file_system/operation_delegate.h" 14#include "chrome/browser/chromeos/drive/file_system_util.h" 15#include "chrome/browser/chromeos/drive/job_scheduler.h" 16#include "chrome/browser/chromeos/drive/resource_metadata.h" 17#include "chrome/browser/chromeos/drive/sync/entry_revert_performer.h" 18#include "chrome/browser/chromeos/drive/sync/remove_performer.h" 19#include "content/public/browser/browser_thread.h" 20#include "google_apis/drive/drive_api_parser.h" 21 22using content::BrowserThread; 23 24namespace drive { 25namespace internal { 26 27struct EntryUpdatePerformer::LocalState { 28 LocalState() : should_content_update(false) { 29 } 30 31 ResourceEntry entry; 32 ResourceEntry parent_entry; 33 base::FilePath drive_file_path; 34 base::FilePath cache_file_path; 35 bool should_content_update; 36}; 37 38namespace { 39 40// Looks up ResourceEntry for source entry and its parent. 41FileError PrepareUpdate(ResourceMetadata* metadata, 42 FileCache* cache, 43 const std::string& local_id, 44 EntryUpdatePerformer::LocalState* local_state) { 45 FileError error = metadata->GetResourceEntryById(local_id, 46 &local_state->entry); 47 if (error != FILE_ERROR_OK) 48 return error; 49 50 error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(), 51 &local_state->parent_entry); 52 if (error != FILE_ERROR_OK) 53 return error; 54 55 error = metadata->GetFilePath(local_id, &local_state->drive_file_path); 56 if (error != FILE_ERROR_OK) 57 return error; 58 59 if (!local_state->entry.file_info().is_directory() && 60 !local_state->entry.file_specific_info().cache_state().is_present() && 61 local_state->entry.resource_id().empty()) { 62 // Locally created file with no cache file, store an empty file. 63 base::FilePath empty_file; 64 if (!base::CreateTemporaryFile(&empty_file)) 65 return FILE_ERROR_FAILED; 66 error = cache->Store(local_id, std::string(), empty_file, 67 FileCache::FILE_OPERATION_MOVE); 68 if (error != FILE_ERROR_OK) 69 return error; 70 error = metadata->GetResourceEntryById(local_id, &local_state->entry); 71 if (error != FILE_ERROR_OK) 72 return error; 73 } 74 75 // Check if content update is needed or not. 76 if (local_state->entry.file_specific_info().cache_state().is_dirty() && 77 !cache->IsOpenedForWrite(local_id)) { 78 // Update cache entry's MD5 if needed. 79 if (local_state->entry.file_specific_info().cache_state().md5().empty()) { 80 error = cache->UpdateMd5(local_id); 81 if (error != FILE_ERROR_OK) 82 return error; 83 error = metadata->GetResourceEntryById(local_id, &local_state->entry); 84 if (error != FILE_ERROR_OK) 85 return error; 86 } 87 88 if (local_state->entry.file_specific_info().cache_state().md5() == 89 local_state->entry.file_specific_info().md5()) { 90 error = cache->ClearDirty(local_id); 91 if (error != FILE_ERROR_OK) 92 return error; 93 } else { 94 error = cache->GetFile(local_id, &local_state->cache_file_path); 95 if (error != FILE_ERROR_OK) 96 return error; 97 98 local_state->should_content_update = true; 99 } 100 } 101 102 // Update metadata_edit_state. 103 switch (local_state->entry.metadata_edit_state()) { 104 case ResourceEntry::CLEAN: // Nothing to do. 105 case ResourceEntry::SYNCING: // Error during the last update. Go ahead. 106 break; 107 108 case ResourceEntry::DIRTY: 109 local_state->entry.set_metadata_edit_state(ResourceEntry::SYNCING); 110 error = metadata->RefreshEntry(local_state->entry); 111 if (error != FILE_ERROR_OK) 112 return error; 113 break; 114 } 115 return FILE_ERROR_OK; 116} 117 118FileError FinishUpdate(ResourceMetadata* metadata, 119 FileCache* cache, 120 const std::string& local_id, 121 scoped_ptr<google_apis::FileResource> file_resource, 122 FileChange* changed_files) { 123 ResourceEntry entry; 124 FileError error = metadata->GetResourceEntryById(local_id, &entry); 125 if (error != FILE_ERROR_OK) 126 return error; 127 128 // When creating new entries, update check may add a new entry with the same 129 // resource ID before us. If such an entry exists, remove it. 130 std::string existing_local_id; 131 error = 132 metadata->GetIdByResourceId(file_resource->file_id(), &existing_local_id); 133 134 switch (error) { 135 case FILE_ERROR_OK: 136 if (existing_local_id != local_id) { 137 base::FilePath existing_entry_path; 138 error = metadata->GetFilePath(existing_local_id, &existing_entry_path); 139 if (error != FILE_ERROR_OK) 140 return error; 141 error = metadata->RemoveEntry(existing_local_id); 142 if (error != FILE_ERROR_OK) 143 return error; 144 changed_files->Update(existing_entry_path, entry, FileChange::DELETE); 145 } 146 break; 147 case FILE_ERROR_NOT_FOUND: 148 break; 149 default: 150 return error; 151 } 152 153 // Update metadata_edit_state and MD5. 154 switch (entry.metadata_edit_state()) { 155 case ResourceEntry::CLEAN: // Nothing to do. 156 case ResourceEntry::DIRTY: // Entry was edited again during the update. 157 break; 158 159 case ResourceEntry::SYNCING: 160 entry.set_metadata_edit_state(ResourceEntry::CLEAN); 161 break; 162 } 163 if (!entry.file_info().is_directory()) 164 entry.mutable_file_specific_info()->set_md5(file_resource->md5_checksum()); 165 entry.set_resource_id(file_resource->file_id()); 166 error = metadata->RefreshEntry(entry); 167 if (error != FILE_ERROR_OK) 168 return error; 169 base::FilePath entry_path; 170 error = metadata->GetFilePath(local_id, &entry_path); 171 if (error != FILE_ERROR_OK) 172 return error; 173 changed_files->Update(entry_path, entry, FileChange::ADD_OR_UPDATE); 174 175 // Clear dirty bit unless the file has been edited during update. 176 if (entry.file_specific_info().cache_state().is_dirty() && 177 entry.file_specific_info().cache_state().md5() == 178 entry.file_specific_info().md5()) { 179 error = cache->ClearDirty(local_id); 180 if (error != FILE_ERROR_OK) 181 return error; 182 } 183 return FILE_ERROR_OK; 184} 185 186} // namespace 187 188EntryUpdatePerformer::EntryUpdatePerformer( 189 base::SequencedTaskRunner* blocking_task_runner, 190 file_system::OperationDelegate* delegate, 191 JobScheduler* scheduler, 192 ResourceMetadata* metadata, 193 FileCache* cache, 194 LoaderController* loader_controller) 195 : blocking_task_runner_(blocking_task_runner), 196 delegate_(delegate), 197 scheduler_(scheduler), 198 metadata_(metadata), 199 cache_(cache), 200 loader_controller_(loader_controller), 201 remove_performer_(new RemovePerformer(blocking_task_runner, 202 delegate, 203 scheduler, 204 metadata)), 205 entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner, 206 delegate, 207 scheduler, 208 metadata)), 209 weak_ptr_factory_(this) { 210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 211} 212 213EntryUpdatePerformer::~EntryUpdatePerformer() { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 215} 216 217void EntryUpdatePerformer::UpdateEntry(const std::string& local_id, 218 const ClientContext& context, 219 const FileOperationCallback& callback) { 220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 221 DCHECK(!callback.is_null()); 222 223 scoped_ptr<LocalState> local_state(new LocalState); 224 LocalState* local_state_ptr = local_state.get(); 225 base::PostTaskAndReplyWithResult( 226 blocking_task_runner_.get(), 227 FROM_HERE, 228 base::Bind(&PrepareUpdate, metadata_, cache_, local_id, local_state_ptr), 229 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare, 230 weak_ptr_factory_.GetWeakPtr(), context, callback, 231 base::Passed(&local_state))); 232} 233 234void EntryUpdatePerformer::UpdateEntryAfterPrepare( 235 const ClientContext& context, 236 const FileOperationCallback& callback, 237 scoped_ptr<LocalState> local_state, 238 FileError error) { 239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 240 DCHECK(!callback.is_null()); 241 242 if (error != FILE_ERROR_OK) { 243 callback.Run(error); 244 return; 245 } 246 247 // Trashed entry should be removed. 248 if (local_state->entry.parent_local_id() == util::kDriveTrashDirLocalId) { 249 remove_performer_->Remove(local_state->entry.local_id(), context, callback); 250 return; 251 } 252 253 // Parent was locally created and needs update. Just return for now. 254 // This entry should be updated again after the parent update completes. 255 if (local_state->parent_entry.resource_id().empty() && 256 local_state->parent_entry.metadata_edit_state() != ResourceEntry::CLEAN) { 257 callback.Run(FILE_ERROR_OK); 258 return; 259 } 260 261 base::Time last_modified = base::Time::FromInternalValue( 262 local_state->entry.file_info().last_modified()); 263 base::Time last_accessed = base::Time::FromInternalValue( 264 local_state->entry.file_info().last_accessed()); 265 266 // Perform content update. 267 if (local_state->should_content_update) { 268 if (local_state->entry.resource_id().empty()) { 269 // Not locking the loader intentionally here to avoid making the UI 270 // unresponsive while uploading large files. 271 // FinishUpdate() is responsible to resolve conflicts caused by this. 272 scoped_ptr<base::ScopedClosureRunner> null_loader_lock; 273 274 DriveUploader::UploadNewFileOptions options; 275 options.modified_date = last_modified; 276 options.last_viewed_by_me_date = last_accessed; 277 scheduler_->UploadNewFile( 278 local_state->parent_entry.resource_id(), 279 local_state->drive_file_path, 280 local_state->cache_file_path, 281 local_state->entry.title(), 282 local_state->entry.file_specific_info().content_mime_type(), 283 options, 284 context, 285 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 286 weak_ptr_factory_.GetWeakPtr(), 287 context, 288 callback, 289 local_state->entry.local_id(), 290 base::Passed(&null_loader_lock))); 291 } else { 292 DriveUploader::UploadExistingFileOptions options; 293 options.title = local_state->entry.title(); 294 options.parent_resource_id = local_state->parent_entry.resource_id(); 295 options.modified_date = last_modified; 296 options.last_viewed_by_me_date = last_accessed; 297 scheduler_->UploadExistingFile( 298 local_state->entry.resource_id(), 299 local_state->drive_file_path, 300 local_state->cache_file_path, 301 local_state->entry.file_specific_info().content_mime_type(), 302 options, 303 context, 304 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 305 weak_ptr_factory_.GetWeakPtr(), 306 context, 307 callback, 308 local_state->entry.local_id(), 309 base::Passed(scoped_ptr<base::ScopedClosureRunner>()))); 310 } 311 return; 312 } 313 314 // Create directory. 315 if (local_state->entry.file_info().is_directory() && 316 local_state->entry.resource_id().empty()) { 317 // Lock the loader to avoid race conditions. 318 scoped_ptr<base::ScopedClosureRunner> loader_lock = 319 loader_controller_->GetLock(); 320 321 DriveServiceInterface::AddNewDirectoryOptions options; 322 options.modified_date = last_modified; 323 options.last_viewed_by_me_date = last_accessed; 324 scheduler_->AddNewDirectory( 325 local_state->parent_entry.resource_id(), 326 local_state->entry.title(), 327 options, 328 context, 329 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 330 weak_ptr_factory_.GetWeakPtr(), 331 context, 332 callback, 333 local_state->entry.local_id(), 334 base::Passed(&loader_lock))); 335 return; 336 } 337 338 // No need to perform update. 339 if (local_state->entry.metadata_edit_state() == ResourceEntry::CLEAN || 340 local_state->entry.resource_id().empty()) { 341 callback.Run(FILE_ERROR_OK); 342 return; 343 } 344 345 // Perform metadata update. 346 scheduler_->UpdateResource( 347 local_state->entry.resource_id(), local_state->parent_entry.resource_id(), 348 local_state->entry.title(), last_modified, last_accessed, 349 context, 350 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 351 weak_ptr_factory_.GetWeakPtr(), 352 context, callback, local_state->entry.local_id(), 353 base::Passed(scoped_ptr<base::ScopedClosureRunner>()))); 354} 355 356void EntryUpdatePerformer::UpdateEntryAfterUpdateResource( 357 const ClientContext& context, 358 const FileOperationCallback& callback, 359 const std::string& local_id, 360 scoped_ptr<base::ScopedClosureRunner> loader_lock, 361 google_apis::GDataErrorCode status, 362 scoped_ptr<google_apis::FileResource> entry) { 363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 364 DCHECK(!callback.is_null()); 365 366 if (status == google_apis::HTTP_FORBIDDEN) { 367 // Editing this entry is not allowed, revert local changes. 368 entry_revert_performer_->RevertEntry(local_id, context, callback); 369 return; 370 } 371 372 FileError error = GDataToFileError(status); 373 if (error != FILE_ERROR_OK) { 374 callback.Run(error); 375 return; 376 } 377 378 FileChange* changed_files = new FileChange; 379 base::PostTaskAndReplyWithResult( 380 blocking_task_runner_.get(), 381 FROM_HERE, 382 base::Bind(&FinishUpdate, 383 metadata_, 384 cache_, 385 local_id, 386 base::Passed(&entry), 387 changed_files), 388 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish, 389 weak_ptr_factory_.GetWeakPtr(), 390 callback, 391 base::Owned(changed_files))); 392} 393 394void EntryUpdatePerformer::UpdateEntryAfterFinish( 395 const FileOperationCallback& callback, 396 const FileChange* changed_files, 397 FileError error) { 398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 399 DCHECK(!callback.is_null()); 400 401 delegate_->OnFileChangedByOperation(*changed_files); 402 callback.Run(error); 403} 404 405} // namespace internal 406} // namespace drive 407