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/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_system/operation_observer.h" 13 #include "chrome/browser/chromeos/drive/file_system_util.h" 14 #include "chrome/browser/chromeos/drive/job_scheduler.h" 15 #include "chrome/browser/chromeos/drive/resource_metadata.h" 16 #include "chrome/browser/chromeos/drive/sync/entry_revert_performer.h" 17 #include "chrome/browser/chromeos/drive/sync/remove_performer.h" 18 #include "content/public/browser/browser_thread.h" 19 #include "google_apis/drive/drive_api_parser.h" 20 #include "google_apis/drive/gdata_wapi_parser.h" 21 22 using content::BrowserThread; 23 24 namespace drive { 25 namespace internal { 26 27 struct 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 38 namespace { 39 40 // Looks up ResourceEntry for source entry and its parent. 41 FileError 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 118 FileError FinishUpdate(ResourceMetadata* metadata, 119 FileCache* cache, 120 const std::string& local_id, 121 scoped_ptr<google_apis::FileResource> file_resource, 122 base::FilePath* changed_directory) { 123 // When creating new entries, update check may add a new entry with the same 124 // resource ID before us. If such an entry exists, remove it. 125 std::string existing_local_id; 126 FileError error = metadata->GetIdByResourceId( 127 file_resource->file_id(), &existing_local_id); 128 switch (error) { 129 case FILE_ERROR_OK: 130 if (existing_local_id != local_id) { 131 base::FilePath existing_entry_path; 132 error = metadata->GetFilePath(existing_local_id, &existing_entry_path); 133 if (error != FILE_ERROR_OK) 134 return error; 135 error = metadata->RemoveEntry(existing_local_id); 136 if (error != FILE_ERROR_OK) 137 return error; 138 *changed_directory = existing_entry_path.DirName(); 139 } 140 break; 141 case FILE_ERROR_NOT_FOUND: 142 break; 143 default: 144 return error; 145 } 146 147 ResourceEntry entry; 148 error = metadata->GetResourceEntryById(local_id, &entry); 149 if (error != FILE_ERROR_OK) 150 return error; 151 152 // Update metadata_edit_state and MD5. 153 switch (entry.metadata_edit_state()) { 154 case ResourceEntry::CLEAN: // Nothing to do. 155 case ResourceEntry::DIRTY: // Entry was edited again during the update. 156 break; 157 158 case ResourceEntry::SYNCING: 159 entry.set_metadata_edit_state(ResourceEntry::CLEAN); 160 break; 161 } 162 if (!entry.file_info().is_directory()) 163 entry.mutable_file_specific_info()->set_md5(file_resource->md5_checksum()); 164 entry.set_resource_id(file_resource->file_id()); 165 error = metadata->RefreshEntry(entry); 166 if (error != FILE_ERROR_OK) 167 return error; 168 169 // Clear dirty bit unless the file has been edited during update. 170 if (entry.file_specific_info().cache_state().is_dirty() && 171 entry.file_specific_info().cache_state().md5() == 172 entry.file_specific_info().md5()) { 173 error = cache->ClearDirty(local_id); 174 if (error != FILE_ERROR_OK) 175 return error; 176 } 177 return FILE_ERROR_OK; 178 } 179 180 } // namespace 181 182 EntryUpdatePerformer::EntryUpdatePerformer( 183 base::SequencedTaskRunner* blocking_task_runner, 184 file_system::OperationObserver* observer, 185 JobScheduler* scheduler, 186 ResourceMetadata* metadata, 187 FileCache* cache, 188 LoaderController* loader_controller) 189 : blocking_task_runner_(blocking_task_runner), 190 observer_(observer), 191 scheduler_(scheduler), 192 metadata_(metadata), 193 cache_(cache), 194 loader_controller_(loader_controller), 195 remove_performer_(new RemovePerformer(blocking_task_runner, 196 observer, 197 scheduler, 198 metadata)), 199 entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner, 200 observer, 201 scheduler, 202 metadata)), 203 weak_ptr_factory_(this) { 204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 205 } 206 207 EntryUpdatePerformer::~EntryUpdatePerformer() { 208 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 209 } 210 211 void EntryUpdatePerformer::UpdateEntry(const std::string& local_id, 212 const ClientContext& context, 213 const FileOperationCallback& callback) { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 215 DCHECK(!callback.is_null()); 216 217 scoped_ptr<LocalState> local_state(new LocalState); 218 LocalState* local_state_ptr = local_state.get(); 219 base::PostTaskAndReplyWithResult( 220 blocking_task_runner_.get(), 221 FROM_HERE, 222 base::Bind(&PrepareUpdate, metadata_, cache_, local_id, local_state_ptr), 223 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare, 224 weak_ptr_factory_.GetWeakPtr(), context, callback, 225 base::Passed(&local_state))); 226 } 227 228 void EntryUpdatePerformer::UpdateEntryAfterPrepare( 229 const ClientContext& context, 230 const FileOperationCallback& callback, 231 scoped_ptr<LocalState> local_state, 232 FileError error) { 233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 234 DCHECK(!callback.is_null()); 235 236 if (error != FILE_ERROR_OK) { 237 callback.Run(error); 238 return; 239 } 240 241 // Trashed entry should be removed. 242 if (local_state->entry.parent_local_id() == util::kDriveTrashDirLocalId) { 243 remove_performer_->Remove(local_state->entry.local_id(), context, callback); 244 return; 245 } 246 247 // Parent was locally created and needs update. Just return for now. 248 // This entry should be updated again after the parent update completes. 249 if (local_state->parent_entry.resource_id().empty() && 250 local_state->parent_entry.metadata_edit_state() != ResourceEntry::CLEAN) { 251 callback.Run(FILE_ERROR_OK); 252 return; 253 } 254 255 base::Time last_modified = base::Time::FromInternalValue( 256 local_state->entry.file_info().last_modified()); 257 base::Time last_accessed = base::Time::FromInternalValue( 258 local_state->entry.file_info().last_accessed()); 259 260 // Perform content update. 261 if (local_state->should_content_update) { 262 if (local_state->entry.resource_id().empty()) { 263 // Not locking the loader intentionally here to avoid making the UI 264 // unresponsive while uploading large files. 265 // FinishUpdate() is responsible to resolve conflicts caused by this. 266 scoped_ptr<base::ScopedClosureRunner> null_loader_lock; 267 268 DriveUploader::UploadNewFileOptions options; 269 options.modified_date = last_modified; 270 options.last_viewed_by_me_date = last_accessed; 271 scheduler_->UploadNewFile( 272 local_state->parent_entry.resource_id(), 273 local_state->drive_file_path, 274 local_state->cache_file_path, 275 local_state->entry.title(), 276 local_state->entry.file_specific_info().content_mime_type(), 277 options, 278 context, 279 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 280 weak_ptr_factory_.GetWeakPtr(), 281 context, 282 callback, 283 local_state->entry.local_id(), 284 base::Passed(&null_loader_lock))); 285 } else { 286 DriveUploader::UploadExistingFileOptions options; 287 options.title = local_state->entry.title(); 288 options.parent_resource_id = local_state->parent_entry.resource_id(); 289 options.modified_date = last_modified; 290 options.last_viewed_by_me_date = last_accessed; 291 scheduler_->UploadExistingFile( 292 local_state->entry.resource_id(), 293 local_state->drive_file_path, 294 local_state->cache_file_path, 295 local_state->entry.file_specific_info().content_mime_type(), 296 options, 297 context, 298 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 299 weak_ptr_factory_.GetWeakPtr(), 300 context, 301 callback, 302 local_state->entry.local_id(), 303 base::Passed(scoped_ptr<base::ScopedClosureRunner>()))); 304 } 305 return; 306 } 307 308 // Create directory. 309 if (local_state->entry.file_info().is_directory() && 310 local_state->entry.resource_id().empty()) { 311 // Lock the loader to avoid race conditions. 312 scoped_ptr<base::ScopedClosureRunner> loader_lock = 313 loader_controller_->GetLock(); 314 315 DriveServiceInterface::AddNewDirectoryOptions options; 316 options.modified_date = last_modified; 317 options.last_viewed_by_me_date = last_accessed; 318 scheduler_->AddNewDirectory( 319 local_state->parent_entry.resource_id(), 320 local_state->entry.title(), 321 options, 322 context, 323 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 324 weak_ptr_factory_.GetWeakPtr(), 325 context, 326 callback, 327 local_state->entry.local_id(), 328 base::Passed(&loader_lock))); 329 return; 330 } 331 332 // No need to perform update. 333 if (local_state->entry.metadata_edit_state() == ResourceEntry::CLEAN || 334 local_state->entry.resource_id().empty()) { 335 callback.Run(FILE_ERROR_OK); 336 return; 337 } 338 339 // Perform metadata update. 340 scheduler_->UpdateResource( 341 local_state->entry.resource_id(), local_state->parent_entry.resource_id(), 342 local_state->entry.title(), last_modified, last_accessed, 343 context, 344 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource, 345 weak_ptr_factory_.GetWeakPtr(), 346 context, callback, local_state->entry.local_id(), 347 base::Passed(scoped_ptr<base::ScopedClosureRunner>()))); 348 } 349 350 void EntryUpdatePerformer::UpdateEntryAfterUpdateResource( 351 const ClientContext& context, 352 const FileOperationCallback& callback, 353 const std::string& local_id, 354 scoped_ptr<base::ScopedClosureRunner> loader_lock, 355 google_apis::GDataErrorCode status, 356 scoped_ptr<google_apis::FileResource> entry) { 357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 358 DCHECK(!callback.is_null()); 359 360 if (status == google_apis::HTTP_FORBIDDEN) { 361 // Editing this entry is not allowed, revert local changes. 362 entry_revert_performer_->RevertEntry(local_id, context, callback); 363 return; 364 } 365 366 FileError error = GDataToFileError(status); 367 if (error != FILE_ERROR_OK) { 368 callback.Run(error); 369 return; 370 } 371 372 base::FilePath* changed_directory = new base::FilePath; 373 base::PostTaskAndReplyWithResult( 374 blocking_task_runner_.get(), 375 FROM_HERE, 376 base::Bind(&FinishUpdate, 377 metadata_, cache_, local_id, base::Passed(&entry), 378 changed_directory), 379 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish, 380 weak_ptr_factory_.GetWeakPtr(), callback, 381 base::Owned(changed_directory))); 382 } 383 384 void EntryUpdatePerformer::UpdateEntryAfterFinish( 385 const FileOperationCallback& callback, 386 const base::FilePath* changed_directory, 387 FileError error) { 388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 389 DCHECK(!callback.is_null()); 390 391 if (!changed_directory->empty()) 392 observer_->OnDirectoryChangedByOperation(*changed_directory); 393 callback.Run(error); 394 } 395 396 } // namespace internal 397 } // namespace drive 398