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/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"
20 using content::BrowserThread;
25 struct EntryUpdatePerformer::LocalState {
26 LocalState() : should_content_update(false) {
30 ResourceEntry parent_entry;
31 base::FilePath drive_file_path;
32 base::FilePath cache_file_path;
33 bool should_content_update;
38 // Looks up ResourceEntry for source entry and its parent.
39 FileError PrepareUpdate(ResourceMetadata* metadata,
41 const std::string& local_id,
42 EntryUpdatePerformer::LocalState* local_state) {
43 FileError error = metadata->GetResourceEntryById(local_id,
45 if (error != FILE_ERROR_OK)
48 error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
49 &local_state->parent_entry);
50 if (error != FILE_ERROR_OK)
53 local_state->drive_file_path = metadata->GetFilePath(local_id);
54 if (local_state->drive_file_path.empty())
55 return FILE_ERROR_NOT_FOUND;
57 FileCacheEntry cache_entry;
58 cache->GetCacheEntry(local_id, &cache_entry);
59 if (!local_state->entry.file_info().is_directory() &&
60 !cache_entry.is_present() && local_state->entry.resource_id().empty()) {
61 // Locally created file with no cache file, store an empty file.
62 base::FilePath empty_file;
63 if (!base::CreateTemporaryFile(&empty_file))
64 return FILE_ERROR_FAILED;
65 error = cache->Store(local_id, std::string(), empty_file,
66 FileCache::FILE_OPERATION_MOVE);
67 if (error != FILE_ERROR_OK)
69 if (!cache->GetCacheEntry(local_id, &cache_entry))
70 return FILE_ERROR_NOT_FOUND;
73 // Check if content update is needed or not.
74 if (cache_entry.is_dirty() && !cache->IsOpenedForWrite(local_id)) {
75 // Update cache entry's MD5 if needed.
76 if (cache_entry.md5().empty()) {
77 error = cache->UpdateMd5(local_id);
78 if (error != FILE_ERROR_OK)
80 if (!cache->GetCacheEntry(local_id, &cache_entry))
81 return FILE_ERROR_NOT_FOUND;
84 if (cache_entry.md5() == local_state->entry.file_specific_info().md5()) {
85 error = cache->ClearDirty(local_id);
86 if (error != FILE_ERROR_OK)
89 error = cache->GetFile(local_id, &local_state->cache_file_path);
90 if (error != FILE_ERROR_OK)
93 local_state->should_content_update = true;
97 // Update metadata_edit_state.
98 switch (local_state->entry.metadata_edit_state()) {
99 case ResourceEntry::CLEAN: // Nothing to do.
100 case ResourceEntry::SYNCING: // Error during the last update. Go ahead.
103 case ResourceEntry::DIRTY:
104 local_state->entry.set_metadata_edit_state(ResourceEntry::SYNCING);
105 error = metadata->RefreshEntry(local_state->entry);
106 if (error != FILE_ERROR_OK)
110 return FILE_ERROR_OK;
113 FileError FinishUpdate(ResourceMetadata* metadata,
115 const std::string& local_id,
116 scoped_ptr<google_apis::ResourceEntry> resource_entry,
117 base::FilePath* changed_directory) {
118 // When creating new entries, update check may add a new entry with the same
119 // resource ID before us. If such an entry exists, remove it.
120 std::string existing_local_id;
121 FileError error = metadata->GetIdByResourceId(
122 resource_entry->resource_id(), &existing_local_id);
125 if (existing_local_id != local_id) {
126 base::FilePath existing_entry_path =
127 metadata->GetFilePath(existing_local_id);
128 error = metadata->RemoveEntry(existing_local_id);
129 if (error != FILE_ERROR_OK)
131 *changed_directory = existing_entry_path.DirName();
134 case FILE_ERROR_NOT_FOUND:
141 error = metadata->GetResourceEntryById(local_id, &entry);
142 if (error != FILE_ERROR_OK)
145 // Update metadata_edit_state and MD5.
146 switch (entry.metadata_edit_state()) {
147 case ResourceEntry::CLEAN: // Nothing to do.
148 case ResourceEntry::DIRTY: // Entry was edited again during the update.
151 case ResourceEntry::SYNCING:
152 entry.set_metadata_edit_state(ResourceEntry::CLEAN);
155 if (!entry.file_info().is_directory())
156 entry.mutable_file_specific_info()->set_md5(resource_entry->file_md5());
157 entry.set_resource_id(resource_entry->resource_id());
158 error = metadata->RefreshEntry(entry);
159 if (error != FILE_ERROR_OK)
162 // Clear dirty bit unless the file has been edited during update.
163 FileCacheEntry cache_entry;
164 if (cache->GetCacheEntry(local_id, &cache_entry) &&
165 cache_entry.is_dirty() &&
166 cache_entry.md5() == entry.file_specific_info().md5()) {
167 error = cache->ClearDirty(local_id);
168 if (error != FILE_ERROR_OK)
171 return FILE_ERROR_OK;
176 EntryUpdatePerformer::EntryUpdatePerformer(
177 base::SequencedTaskRunner* blocking_task_runner,
178 file_system::OperationObserver* observer,
179 JobScheduler* scheduler,
180 ResourceMetadata* metadata,
182 LoaderController* loader_controller)
183 : blocking_task_runner_(blocking_task_runner),
185 scheduler_(scheduler),
188 loader_controller_(loader_controller),
189 remove_performer_(new RemovePerformer(blocking_task_runner,
193 entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
197 weak_ptr_factory_(this) {
198 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201 EntryUpdatePerformer::~EntryUpdatePerformer() {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
205 void EntryUpdatePerformer::UpdateEntry(const std::string& local_id,
206 const ClientContext& context,
207 const FileOperationCallback& callback) {
208 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
209 DCHECK(!callback.is_null());
211 scoped_ptr<LocalState> local_state(new LocalState);
212 LocalState* local_state_ptr = local_state.get();
213 base::PostTaskAndReplyWithResult(
214 blocking_task_runner_.get(),
216 base::Bind(&PrepareUpdate, metadata_, cache_, local_id, local_state_ptr),
217 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare,
218 weak_ptr_factory_.GetWeakPtr(), context, callback,
219 base::Passed(&local_state)));
222 void EntryUpdatePerformer::UpdateEntryAfterPrepare(
223 const ClientContext& context,
224 const FileOperationCallback& callback,
225 scoped_ptr<LocalState> local_state,
227 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
228 DCHECK(!callback.is_null());
230 if (error != FILE_ERROR_OK) {
235 // Trashed entry should be removed.
236 if (local_state->entry.parent_local_id() == util::kDriveTrashDirLocalId) {
237 remove_performer_->Remove(local_state->entry.local_id(), context, callback);
241 // Parent was locally created and needs update. Just return for now.
242 // This entry should be updated again after the parent update completes.
243 if (local_state->parent_entry.resource_id().empty() &&
244 local_state->parent_entry.metadata_edit_state() != ResourceEntry::CLEAN) {
245 callback.Run(FILE_ERROR_OK);
249 base::Time last_modified = base::Time::FromInternalValue(
250 local_state->entry.file_info().last_modified());
251 base::Time last_accessed = base::Time::FromInternalValue(
252 local_state->entry.file_info().last_accessed());
254 // Perform content update.
255 if (local_state->should_content_update) {
256 if (local_state->entry.resource_id().empty()) {
257 // Not locking the loader intentionally here to avoid making the UI
258 // unresponsive while uploading large files.
259 // FinishUpdate() is responsible to resolve conflicts caused by this.
260 scoped_ptr<base::ScopedClosureRunner> null_loader_lock;
262 DriveUploader::UploadNewFileOptions options;
263 options.modified_date = last_modified;
264 options.last_viewed_by_me_date = last_accessed;
265 scheduler_->UploadNewFile(
266 local_state->parent_entry.resource_id(),
267 local_state->drive_file_path,
268 local_state->cache_file_path,
269 local_state->entry.title(),
270 local_state->entry.file_specific_info().content_mime_type(),
273 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
274 weak_ptr_factory_.GetWeakPtr(),
277 local_state->entry.local_id(),
278 base::Passed(&null_loader_lock)));
280 DriveUploader::UploadExistingFileOptions options;
281 options.title = local_state->entry.title();
282 options.parent_resource_id = local_state->parent_entry.resource_id();
283 options.modified_date = last_modified;
284 options.last_viewed_by_me_date = last_accessed;
285 scheduler_->UploadExistingFile(
286 local_state->entry.resource_id(),
287 local_state->drive_file_path,
288 local_state->cache_file_path,
289 local_state->entry.file_specific_info().content_mime_type(),
292 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
293 weak_ptr_factory_.GetWeakPtr(),
296 local_state->entry.local_id(),
297 base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
303 if (local_state->entry.file_info().is_directory() &&
304 local_state->entry.resource_id().empty()) {
305 // Lock the loader to avoid race conditions.
306 scoped_ptr<base::ScopedClosureRunner> loader_lock =
307 loader_controller_->GetLock();
309 DriveServiceInterface::AddNewDirectoryOptions options;
310 options.modified_date = last_modified;
311 options.last_viewed_by_me_date = last_accessed;
312 scheduler_->AddNewDirectory(
313 local_state->parent_entry.resource_id(),
314 local_state->entry.title(),
317 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
318 weak_ptr_factory_.GetWeakPtr(),
321 local_state->entry.local_id(),
322 base::Passed(&loader_lock)));
326 // No need to perform update.
327 if (local_state->entry.metadata_edit_state() == ResourceEntry::CLEAN ||
328 local_state->entry.resource_id().empty()) {
329 callback.Run(FILE_ERROR_OK);
333 // Perform metadata update.
334 scheduler_->UpdateResource(
335 local_state->entry.resource_id(), local_state->parent_entry.resource_id(),
336 local_state->entry.title(), last_modified, last_accessed,
338 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
339 weak_ptr_factory_.GetWeakPtr(),
340 context, callback, local_state->entry.local_id(),
341 base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
344 void EntryUpdatePerformer::UpdateEntryAfterUpdateResource(
345 const ClientContext& context,
346 const FileOperationCallback& callback,
347 const std::string& local_id,
348 scoped_ptr<base::ScopedClosureRunner> loader_lock,
349 google_apis::GDataErrorCode status,
350 scoped_ptr<google_apis::ResourceEntry> resource_entry) {
351 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
352 DCHECK(!callback.is_null());
354 if (status == google_apis::HTTP_FORBIDDEN) {
355 // Editing this entry is not allowed, revert local changes.
356 entry_revert_performer_->RevertEntry(local_id, context, callback);
360 FileError error = GDataToFileError(status);
361 if (error != FILE_ERROR_OK) {
366 base::FilePath* changed_directory = new base::FilePath;
367 base::PostTaskAndReplyWithResult(
368 blocking_task_runner_.get(),
370 base::Bind(&FinishUpdate,
371 metadata_, cache_, local_id, base::Passed(&resource_entry),
373 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
374 weak_ptr_factory_.GetWeakPtr(), callback,
375 base::Owned(changed_directory)));
378 void EntryUpdatePerformer::UpdateEntryAfterFinish(
379 const FileOperationCallback& callback,
380 const base::FilePath* changed_directory,
382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
383 DCHECK(!callback.is_null());
385 if (!changed_directory->empty())
386 observer_->OnDirectoryChangedByOperation(*changed_directory);
390 } // namespace internal