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"
9 #include "base/callback_helpers.h"
10 #include "base/files/file_util.h"
11 #include "chrome/browser/chromeos/drive/change_list_loader.h"
12 #include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
13 #include "chrome/browser/chromeos/drive/sync/entry_revert_performer.h"
14 #include "chrome/browser/chromeos/drive/sync/remove_performer.h"
15 #include "components/drive/drive.pb.h"
16 #include "components/drive/file_cache.h"
17 #include "components/drive/file_change.h"
18 #include "components/drive/file_system_core_util.h"
19 #include "components/drive/job_scheduler.h"
20 #include "components/drive/resource_metadata.h"
21 #include "google_apis/drive/drive_api_parser.h"
26 struct EntryUpdatePerformer::LocalState
{
27 LocalState() : cache_file_size(0), should_content_update(false) {}
30 ResourceEntry parent_entry
;
31 base::FilePath drive_file_path
;
32 base::FilePath cache_file_path
;
33 int64 cache_file_size
;
34 bool should_content_update
;
39 struct PropertyCompare
{
40 bool operator()(const drive::Property
& x
, const drive::Property
& y
) const {
41 if (x
.key() < y
.key())
43 if (x
.key() > y
.key())
45 if (x
.value() < y
.value())
47 if (y
.value() > y
.value())
49 return x
.visibility() < y
.visibility();
53 // Looks up ResourceEntry for source entry and its parent.
54 FileError
PrepareUpdate(ResourceMetadata
* metadata
,
56 const std::string
& local_id
,
57 EntryUpdatePerformer::LocalState
* local_state
) {
58 FileError error
= metadata
->GetResourceEntryById(local_id
,
60 if (error
!= FILE_ERROR_OK
)
63 error
= metadata
->GetResourceEntryById(local_state
->entry
.parent_local_id(),
64 &local_state
->parent_entry
);
65 if (error
!= FILE_ERROR_OK
)
68 error
= metadata
->GetFilePath(local_id
, &local_state
->drive_file_path
);
69 if (error
!= FILE_ERROR_OK
)
72 if (!local_state
->entry
.file_info().is_directory() &&
73 !local_state
->entry
.file_specific_info().cache_state().is_present() &&
74 local_state
->entry
.resource_id().empty()) {
75 // Locally created file with no cache file, store an empty file.
76 base::FilePath empty_file
;
77 if (!base::CreateTemporaryFile(&empty_file
))
78 return FILE_ERROR_FAILED
;
79 error
= cache
->Store(local_id
, std::string(), empty_file
,
80 FileCache::FILE_OPERATION_MOVE
);
81 if (error
!= FILE_ERROR_OK
)
83 error
= metadata
->GetResourceEntryById(local_id
, &local_state
->entry
);
84 if (error
!= FILE_ERROR_OK
)
88 // Check if content update is needed or not.
89 if (local_state
->entry
.file_specific_info().cache_state().is_dirty() &&
90 !cache
->IsOpenedForWrite(local_id
)) {
91 // Update cache entry's MD5 if needed.
92 if (local_state
->entry
.file_specific_info().cache_state().md5().empty()) {
93 error
= cache
->UpdateMd5(local_id
);
94 if (error
!= FILE_ERROR_OK
)
96 error
= metadata
->GetResourceEntryById(local_id
, &local_state
->entry
);
97 if (error
!= FILE_ERROR_OK
)
101 if (local_state
->entry
.file_specific_info().cache_state().md5() ==
102 local_state
->entry
.file_specific_info().md5()) {
103 error
= cache
->ClearDirty(local_id
);
104 if (error
!= FILE_ERROR_OK
)
107 error
= cache
->GetFile(local_id
, &local_state
->cache_file_path
);
108 if (error
!= FILE_ERROR_OK
)
110 const bool result
= base::GetFileSize(local_state
->cache_file_path
,
111 &local_state
->cache_file_size
);
113 return FILE_ERROR_FAILED
;
114 local_state
->should_content_update
= true;
118 // Update metadata_edit_state.
119 switch (local_state
->entry
.metadata_edit_state()) {
120 case ResourceEntry::CLEAN
: // Nothing to do.
121 case ResourceEntry::SYNCING
: // Error during the last update. Go ahead.
124 case ResourceEntry::DIRTY
:
125 local_state
->entry
.set_metadata_edit_state(ResourceEntry::SYNCING
);
126 error
= metadata
->RefreshEntry(local_state
->entry
);
127 if (error
!= FILE_ERROR_OK
)
131 return FILE_ERROR_OK
;
134 FileError
FinishUpdate(ResourceMetadata
* metadata
,
136 scoped_ptr
<EntryUpdatePerformer::LocalState
> local_state
,
137 scoped_ptr
<google_apis::FileResource
> file_resource
,
138 FileChange
* changed_files
) {
141 metadata
->GetResourceEntryById(local_state
->entry
.local_id(), &entry
);
142 if (error
!= FILE_ERROR_OK
)
145 // When creating new entries, update check may add a new entry with the same
146 // resource ID before us. If such an entry exists, remove it.
147 std::string existing_local_id
;
149 metadata
->GetIdByResourceId(file_resource
->file_id(), &existing_local_id
);
153 if (existing_local_id
!= local_state
->entry
.local_id()) {
154 base::FilePath existing_entry_path
;
155 error
= metadata
->GetFilePath(existing_local_id
, &existing_entry_path
);
156 if (error
!= FILE_ERROR_OK
)
158 error
= metadata
->RemoveEntry(existing_local_id
);
159 if (error
!= FILE_ERROR_OK
)
161 changed_files
->Update(existing_entry_path
, entry
,
162 FileChange::CHANGE_TYPE_DELETE
);
165 case FILE_ERROR_NOT_FOUND
:
171 // Update metadata_edit_state and MD5.
172 switch (entry
.metadata_edit_state()) {
173 case ResourceEntry::CLEAN
: // Nothing to do.
174 case ResourceEntry::DIRTY
: // Entry was edited again during the update.
177 case ResourceEntry::SYNCING
:
178 entry
.set_metadata_edit_state(ResourceEntry::CLEAN
);
181 if (!entry
.file_info().is_directory())
182 entry
.mutable_file_specific_info()->set_md5(file_resource
->md5_checksum());
183 entry
.set_resource_id(file_resource
->file_id());
185 // Keep only those properties which have been added or changed in the proto
186 // during the update.
187 std::set
<drive::Property
, PropertyCompare
> synced_properties(
188 local_state
->entry
.new_properties().begin(),
189 local_state
->entry
.new_properties().end());
191 google::protobuf::RepeatedPtrField
<drive::Property
> not_synced_properties
;
192 for (const auto& property
: entry
.new_properties()) {
193 if (!synced_properties
.count(property
)) {
194 Property
* const not_synced_property
= not_synced_properties
.Add();
195 not_synced_property
->CopyFrom(property
);
198 entry
.mutable_new_properties()->Swap(¬_synced_properties
);
200 error
= metadata
->RefreshEntry(entry
);
201 if (error
!= FILE_ERROR_OK
)
203 base::FilePath entry_path
;
204 error
= metadata
->GetFilePath(local_state
->entry
.local_id(), &entry_path
);
205 if (error
!= FILE_ERROR_OK
)
207 changed_files
->Update(entry_path
, entry
,
208 FileChange::CHANGE_TYPE_ADD_OR_UPDATE
);
210 // Clear dirty bit unless the file has been edited during update.
211 if (entry
.file_specific_info().cache_state().is_dirty() &&
212 entry
.file_specific_info().cache_state().md5() ==
213 entry
.file_specific_info().md5()) {
214 error
= cache
->ClearDirty(local_state
->entry
.local_id());
215 if (error
!= FILE_ERROR_OK
)
218 return FILE_ERROR_OK
;
223 EntryUpdatePerformer::EntryUpdatePerformer(
224 base::SequencedTaskRunner
* blocking_task_runner
,
225 file_system::OperationDelegate
* delegate
,
226 JobScheduler
* scheduler
,
227 ResourceMetadata
* metadata
,
229 LoaderController
* loader_controller
)
230 : blocking_task_runner_(blocking_task_runner
),
232 scheduler_(scheduler
),
235 loader_controller_(loader_controller
),
236 remove_performer_(new RemovePerformer(blocking_task_runner
,
240 entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner
,
244 weak_ptr_factory_(this) {
247 EntryUpdatePerformer::~EntryUpdatePerformer() {
248 DCHECK(thread_checker_
.CalledOnValidThread());
251 void EntryUpdatePerformer::UpdateEntry(const std::string
& local_id
,
252 const ClientContext
& context
,
253 const FileOperationCallback
& callback
) {
254 DCHECK(thread_checker_
.CalledOnValidThread());
255 DCHECK(!callback
.is_null());
257 scoped_ptr
<LocalState
> local_state(new LocalState
);
258 LocalState
* const local_state_ptr
= local_state
.get();
259 base::PostTaskAndReplyWithResult(
260 blocking_task_runner_
.get(),
262 base::Bind(&PrepareUpdate
, metadata_
, cache_
, local_id
, local_state_ptr
),
263 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare
,
264 weak_ptr_factory_
.GetWeakPtr(), context
, callback
,
265 base::Passed(&local_state
)));
268 void EntryUpdatePerformer::UpdateEntryAfterPrepare(
269 const ClientContext
& context
,
270 const FileOperationCallback
& callback
,
271 scoped_ptr
<LocalState
> local_state
,
273 DCHECK(thread_checker_
.CalledOnValidThread());
274 DCHECK(!callback
.is_null());
276 if (error
!= FILE_ERROR_OK
) {
281 // Trashed entry should be removed.
282 if (local_state
->entry
.parent_local_id() == util::kDriveTrashDirLocalId
) {
283 remove_performer_
->Remove(local_state
->entry
.local_id(), context
, callback
);
287 // Parent was locally created and needs update. Just return for now.
288 // This entry should be updated again after the parent update completes.
289 if (local_state
->parent_entry
.resource_id().empty() &&
290 local_state
->parent_entry
.metadata_edit_state() != ResourceEntry::CLEAN
) {
291 callback
.Run(FILE_ERROR_OK
);
295 base::Time last_modified
= base::Time::FromInternalValue(
296 local_state
->entry
.file_info().last_modified());
297 base::Time last_accessed
= base::Time::FromInternalValue(
298 local_state
->entry
.file_info().last_accessed());
300 // Compose a list of new properties from the proto.
301 google_apis::drive::Properties properties
;
302 for (const auto& proto_property
: local_state
->entry
.new_properties()) {
303 google_apis::drive::Property property
;
304 switch (proto_property
.visibility()) {
305 case Property_Visibility_PRIVATE
:
306 property
.set_visibility(
307 google_apis::drive::Property::VISIBILITY_PRIVATE
);
309 case Property_Visibility_PUBLIC
:
310 property
.set_visibility(
311 google_apis::drive::Property::VISIBILITY_PUBLIC
);
314 property
.set_key(proto_property
.key());
315 property
.set_value(proto_property
.value());
316 properties
.push_back(property
);
319 // Perform content update.
320 if (local_state
->should_content_update
) {
321 if (local_state
->entry
.resource_id().empty()) {
322 // Not locking the loader intentionally here to avoid making the UI
323 // unresponsive while uploading large files.
324 // FinishUpdate() is responsible to resolve conflicts caused by this.
325 scoped_ptr
<base::ScopedClosureRunner
> null_loader_lock
;
327 UploadNewFileOptions options
;
328 options
.modified_date
= last_modified
;
329 options
.last_viewed_by_me_date
= last_accessed
;
330 options
.properties
= properties
;
331 LocalState
* const local_state_ptr
= local_state
.get();
332 scheduler_
->UploadNewFile(
333 local_state_ptr
->parent_entry
.resource_id(),
334 local_state_ptr
->cache_file_size
, local_state_ptr
->drive_file_path
,
335 local_state_ptr
->cache_file_path
, local_state_ptr
->entry
.title(),
336 local_state_ptr
->entry
.file_specific_info().content_mime_type(),
338 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource
,
339 weak_ptr_factory_
.GetWeakPtr(), context
, callback
,
340 base::Passed(&local_state
),
341 base::Passed(&null_loader_lock
)));
343 UploadExistingFileOptions options
;
344 options
.title
= local_state
->entry
.title();
345 options
.parent_resource_id
= local_state
->parent_entry
.resource_id();
346 options
.modified_date
= last_modified
;
347 options
.last_viewed_by_me_date
= last_accessed
;
348 options
.properties
= properties
;
349 LocalState
* const local_state_ptr
= local_state
.get();
350 scheduler_
->UploadExistingFile(
351 local_state_ptr
->entry
.resource_id(),
352 local_state_ptr
->cache_file_size
, local_state_ptr
->drive_file_path
,
353 local_state_ptr
->cache_file_path
,
354 local_state_ptr
->entry
.file_specific_info().content_mime_type(),
356 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource
,
357 weak_ptr_factory_
.GetWeakPtr(), context
, callback
,
358 base::Passed(&local_state
),
359 base::Passed(scoped_ptr
<base::ScopedClosureRunner
>())));
365 if (local_state
->entry
.file_info().is_directory() &&
366 local_state
->entry
.resource_id().empty()) {
367 // Lock the loader to avoid race conditions.
368 scoped_ptr
<base::ScopedClosureRunner
> loader_lock
=
369 loader_controller_
->GetLock();
371 AddNewDirectoryOptions options
;
372 options
.modified_date
= last_modified
;
373 options
.last_viewed_by_me_date
= last_accessed
;
374 options
.properties
= properties
;
375 LocalState
* const local_state_ptr
= local_state
.get();
376 scheduler_
->AddNewDirectory(
377 local_state_ptr
->parent_entry
.resource_id(),
378 local_state_ptr
->entry
.title(), options
, context
,
379 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource
,
380 weak_ptr_factory_
.GetWeakPtr(), context
, callback
,
381 base::Passed(&local_state
), base::Passed(&loader_lock
)));
385 // No need to perform update.
386 if (local_state
->entry
.metadata_edit_state() == ResourceEntry::CLEAN
||
387 local_state
->entry
.resource_id().empty()) {
388 callback
.Run(FILE_ERROR_OK
);
392 // Perform metadata update.
393 LocalState
* const local_state_ptr
= local_state
.get();
394 scheduler_
->UpdateResource(
395 local_state_ptr
->entry
.resource_id(),
396 local_state_ptr
->parent_entry
.resource_id(),
397 local_state_ptr
->entry
.title(), last_modified
, last_accessed
, properties
,
399 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource
,
400 weak_ptr_factory_
.GetWeakPtr(), context
, callback
,
401 base::Passed(&local_state
),
402 base::Passed(scoped_ptr
<base::ScopedClosureRunner
>())));
405 void EntryUpdatePerformer::UpdateEntryAfterUpdateResource(
406 const ClientContext
& context
,
407 const FileOperationCallback
& callback
,
408 scoped_ptr
<LocalState
> local_state
,
409 scoped_ptr
<base::ScopedClosureRunner
> loader_lock
,
410 google_apis::DriveApiErrorCode status
,
411 scoped_ptr
<google_apis::FileResource
> entry
) {
412 DCHECK(thread_checker_
.CalledOnValidThread());
413 DCHECK(!callback
.is_null());
415 if (status
== google_apis::HTTP_FORBIDDEN
) {
416 // Editing this entry is not allowed, revert local changes.
417 entry_revert_performer_
->RevertEntry(local_state
->entry
.local_id(), context
,
422 FileError error
= GDataToFileError(status
);
423 if (error
!= FILE_ERROR_OK
) {
428 FileChange
* changed_files
= new FileChange
;
429 base::PostTaskAndReplyWithResult(
430 blocking_task_runner_
.get(), FROM_HERE
,
431 base::Bind(&FinishUpdate
, metadata_
, cache_
, base::Passed(&local_state
),
432 base::Passed(&entry
), changed_files
),
433 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish
,
434 weak_ptr_factory_
.GetWeakPtr(), callback
,
435 base::Owned(changed_files
)));
438 void EntryUpdatePerformer::UpdateEntryAfterFinish(
439 const FileOperationCallback
& callback
,
440 const FileChange
* changed_files
,
442 DCHECK(thread_checker_
.CalledOnValidThread());
443 DCHECK(!callback
.is_null());
445 delegate_
->OnFileChangedByOperation(*changed_files
);
449 } // namespace internal