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.
5 #include "chrome/browser/chromeos/drive/sync/entry_update_performer.h"
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"
22 using content::BrowserThread;
27 struct EntryUpdatePerformer::LocalState {
28 LocalState() : should_content_update(false) {
32 ResourceEntry parent_entry;
33 base::FilePath drive_file_path;
34 base::FilePath cache_file_path;
35 bool should_content_update;
40 // Looks up ResourceEntry for source entry and its parent.
41 FileError PrepareUpdate(ResourceMetadata* metadata,
43 const std::string& local_id,
44 EntryUpdatePerformer::LocalState* local_state) {
45 FileError error = metadata->GetResourceEntryById(local_id,
47 if (error != FILE_ERROR_OK)
50 error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
51 &local_state->parent_entry);
52 if (error != FILE_ERROR_OK)
55 error = metadata->GetFilePath(local_id, &local_state->drive_file_path);
56 if (error != FILE_ERROR_OK)
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)
70 error = metadata->GetResourceEntryById(local_id, &local_state->entry);
71 if (error != FILE_ERROR_OK)
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)
83 error = metadata->GetResourceEntryById(local_id, &local_state->entry);
84 if (error != FILE_ERROR_OK)
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)
94 error = cache->GetFile(local_id, &local_state->cache_file_path);
95 if (error != FILE_ERROR_OK)
98 local_state->should_content_update = true;
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.
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)
115 return FILE_ERROR_OK;
118 FileError FinishUpdate(ResourceMetadata* metadata,
120 const std::string& local_id,
121 scoped_ptr<google_apis::FileResource> file_resource,
122 FileChange* changed_files) {
124 FileError error = metadata->GetResourceEntryById(local_id, &entry);
125 if (error != FILE_ERROR_OK)
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;
132 metadata->GetIdByResourceId(file_resource->file_id(), &existing_local_id);
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)
141 error = metadata->RemoveEntry(existing_local_id);
142 if (error != FILE_ERROR_OK)
144 changed_files->Update(existing_entry_path, entry, FileChange::DELETE);
147 case FILE_ERROR_NOT_FOUND:
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.
159 case ResourceEntry::SYNCING:
160 entry.set_metadata_edit_state(ResourceEntry::CLEAN);
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)
169 base::FilePath entry_path;
170 error = metadata->GetFilePath(local_id, &entry_path);
171 if (error != FILE_ERROR_OK)
173 changed_files->Update(entry_path, entry, FileChange::ADD_OR_UPDATE);
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)
183 return FILE_ERROR_OK;
188 EntryUpdatePerformer::EntryUpdatePerformer(
189 base::SequencedTaskRunner* blocking_task_runner,
190 file_system::OperationDelegate* delegate,
191 JobScheduler* scheduler,
192 ResourceMetadata* metadata,
194 LoaderController* loader_controller)
195 : blocking_task_runner_(blocking_task_runner),
197 scheduler_(scheduler),
200 loader_controller_(loader_controller),
201 remove_performer_(new RemovePerformer(blocking_task_runner,
205 entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
209 weak_ptr_factory_(this) {
210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
213 EntryUpdatePerformer::~EntryUpdatePerformer() {
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
217 void 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());
223 scoped_ptr<LocalState> local_state(new LocalState);
224 LocalState* local_state_ptr = local_state.get();
225 base::PostTaskAndReplyWithResult(
226 blocking_task_runner_.get(),
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)));
234 void EntryUpdatePerformer::UpdateEntryAfterPrepare(
235 const ClientContext& context,
236 const FileOperationCallback& callback,
237 scoped_ptr<LocalState> local_state,
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240 DCHECK(!callback.is_null());
242 if (error != FILE_ERROR_OK) {
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);
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);
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());
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;
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(),
285 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
286 weak_ptr_factory_.GetWeakPtr(),
289 local_state->entry.local_id(),
290 base::Passed(&null_loader_lock)));
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(),
304 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
305 weak_ptr_factory_.GetWeakPtr(),
308 local_state->entry.local_id(),
309 base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
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();
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(),
329 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
330 weak_ptr_factory_.GetWeakPtr(),
333 local_state->entry.local_id(),
334 base::Passed(&loader_lock)));
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);
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,
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>())));
356 void 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());
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);
372 FileError error = GDataToFileError(status);
373 if (error != FILE_ERROR_OK) {
378 FileChange* changed_files = new FileChange;
379 base::PostTaskAndReplyWithResult(
380 blocking_task_runner_.get(),
382 base::Bind(&FinishUpdate,
386 base::Passed(&entry),
388 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
389 weak_ptr_factory_.GetWeakPtr(),
391 base::Owned(changed_files)));
394 void EntryUpdatePerformer::UpdateEntryAfterFinish(
395 const FileOperationCallback& callback,
396 const FileChange* changed_files,
398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
399 DCHECK(!callback.is_null());
401 delegate_->OnFileChangedByOperation(*changed_files);
405 } // namespace internal