Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / chromeos / drive / sync / entry_update_performer.cc
blobc77f3db3051b76bd3f4b7504c9fef1bb69439e0e
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 <set>
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/drive.pb.h"
13 #include "chrome/browser/chromeos/drive/file_cache.h"
14 #include "chrome/browser/chromeos/drive/file_change.h"
15 #include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
16 #include "chrome/browser/chromeos/drive/file_system_util.h"
17 #include "chrome/browser/chromeos/drive/job_scheduler.h"
18 #include "chrome/browser/chromeos/drive/resource_metadata.h"
19 #include "chrome/browser/chromeos/drive/sync/entry_revert_performer.h"
20 #include "chrome/browser/chromeos/drive/sync/remove_performer.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "google_apis/drive/drive_api_parser.h"
24 using content::BrowserThread;
26 namespace drive {
27 namespace internal {
29 struct EntryUpdatePerformer::LocalState {
30 LocalState() : should_content_update(false) {
33 ResourceEntry entry;
34 ResourceEntry parent_entry;
35 base::FilePath drive_file_path;
36 base::FilePath cache_file_path;
37 bool should_content_update;
40 namespace {
42 struct PropertyCompare {
43 bool operator()(const drive::Property& x, const drive::Property& y) const {
44 if (x.key() < y.key())
45 return true;
46 if (x.key() > y.key())
47 return false;
48 if (x.value() < y.value())
49 return true;
50 if (y.value() > y.value())
51 return false;
52 return x.visibility() < y.visibility();
56 // Looks up ResourceEntry for source entry and its parent.
57 FileError PrepareUpdate(ResourceMetadata* metadata,
58 FileCache* cache,
59 const std::string& local_id,
60 EntryUpdatePerformer::LocalState* local_state) {
61 FileError error = metadata->GetResourceEntryById(local_id,
62 &local_state->entry);
63 if (error != FILE_ERROR_OK)
64 return error;
66 error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
67 &local_state->parent_entry);
68 if (error != FILE_ERROR_OK)
69 return error;
71 error = metadata->GetFilePath(local_id, &local_state->drive_file_path);
72 if (error != FILE_ERROR_OK)
73 return error;
75 if (!local_state->entry.file_info().is_directory() &&
76 !local_state->entry.file_specific_info().cache_state().is_present() &&
77 local_state->entry.resource_id().empty()) {
78 // Locally created file with no cache file, store an empty file.
79 base::FilePath empty_file;
80 if (!base::CreateTemporaryFile(&empty_file))
81 return FILE_ERROR_FAILED;
82 error = cache->Store(local_id, std::string(), empty_file,
83 FileCache::FILE_OPERATION_MOVE);
84 if (error != FILE_ERROR_OK)
85 return error;
86 error = metadata->GetResourceEntryById(local_id, &local_state->entry);
87 if (error != FILE_ERROR_OK)
88 return error;
91 // Check if content update is needed or not.
92 if (local_state->entry.file_specific_info().cache_state().is_dirty() &&
93 !cache->IsOpenedForWrite(local_id)) {
94 // Update cache entry's MD5 if needed.
95 if (local_state->entry.file_specific_info().cache_state().md5().empty()) {
96 error = cache->UpdateMd5(local_id);
97 if (error != FILE_ERROR_OK)
98 return error;
99 error = metadata->GetResourceEntryById(local_id, &local_state->entry);
100 if (error != FILE_ERROR_OK)
101 return error;
104 if (local_state->entry.file_specific_info().cache_state().md5() ==
105 local_state->entry.file_specific_info().md5()) {
106 error = cache->ClearDirty(local_id);
107 if (error != FILE_ERROR_OK)
108 return error;
109 } else {
110 error = cache->GetFile(local_id, &local_state->cache_file_path);
111 if (error != FILE_ERROR_OK)
112 return error;
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.
122 break;
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)
128 return error;
129 break;
131 return FILE_ERROR_OK;
134 FileError FinishUpdate(ResourceMetadata* metadata,
135 FileCache* cache,
136 scoped_ptr<EntryUpdatePerformer::LocalState> local_state,
137 scoped_ptr<google_apis::FileResource> file_resource,
138 FileChange* changed_files) {
139 ResourceEntry entry;
140 FileError error =
141 metadata->GetResourceEntryById(local_state->entry.local_id(), &entry);
142 if (error != FILE_ERROR_OK)
143 return error;
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;
148 error =
149 metadata->GetIdByResourceId(file_resource->file_id(), &existing_local_id);
151 switch (error) {
152 case FILE_ERROR_OK:
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)
157 return error;
158 error = metadata->RemoveEntry(existing_local_id);
159 if (error != FILE_ERROR_OK)
160 return error;
161 changed_files->Update(existing_entry_path, entry, FileChange::DELETE);
163 break;
164 case FILE_ERROR_NOT_FOUND:
165 break;
166 default:
167 return error;
170 // Update metadata_edit_state and MD5.
171 switch (entry.metadata_edit_state()) {
172 case ResourceEntry::CLEAN: // Nothing to do.
173 case ResourceEntry::DIRTY: // Entry was edited again during the update.
174 break;
176 case ResourceEntry::SYNCING:
177 entry.set_metadata_edit_state(ResourceEntry::CLEAN);
178 break;
180 if (!entry.file_info().is_directory())
181 entry.mutable_file_specific_info()->set_md5(file_resource->md5_checksum());
182 entry.set_resource_id(file_resource->file_id());
184 // Keep only those properties which have been added or changed in the proto
185 // during the update.
186 std::set<drive::Property, PropertyCompare> synced_properties(
187 local_state->entry.new_properties().begin(),
188 local_state->entry.new_properties().end());
190 google::protobuf::RepeatedPtrField<drive::Property> not_synced_properties;
191 for (const auto& property : entry.new_properties()) {
192 if (!synced_properties.count(property)) {
193 Property* const not_synced_property = not_synced_properties.Add();
194 not_synced_property->CopyFrom(property);
197 entry.mutable_new_properties()->Swap(&not_synced_properties);
199 error = metadata->RefreshEntry(entry);
200 if (error != FILE_ERROR_OK)
201 return error;
202 base::FilePath entry_path;
203 error = metadata->GetFilePath(local_state->entry.local_id(), &entry_path);
204 if (error != FILE_ERROR_OK)
205 return error;
206 changed_files->Update(entry_path, entry, FileChange::ADD_OR_UPDATE);
208 // Clear dirty bit unless the file has been edited during update.
209 if (entry.file_specific_info().cache_state().is_dirty() &&
210 entry.file_specific_info().cache_state().md5() ==
211 entry.file_specific_info().md5()) {
212 error = cache->ClearDirty(local_state->entry.local_id());
213 if (error != FILE_ERROR_OK)
214 return error;
216 return FILE_ERROR_OK;
219 } // namespace
221 EntryUpdatePerformer::EntryUpdatePerformer(
222 base::SequencedTaskRunner* blocking_task_runner,
223 file_system::OperationDelegate* delegate,
224 JobScheduler* scheduler,
225 ResourceMetadata* metadata,
226 FileCache* cache,
227 LoaderController* loader_controller)
228 : blocking_task_runner_(blocking_task_runner),
229 delegate_(delegate),
230 scheduler_(scheduler),
231 metadata_(metadata),
232 cache_(cache),
233 loader_controller_(loader_controller),
234 remove_performer_(new RemovePerformer(blocking_task_runner,
235 delegate,
236 scheduler,
237 metadata)),
238 entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
239 delegate,
240 scheduler,
241 metadata)),
242 weak_ptr_factory_(this) {
243 DCHECK_CURRENTLY_ON(BrowserThread::UI);
246 EntryUpdatePerformer::~EntryUpdatePerformer() {
247 DCHECK_CURRENTLY_ON(BrowserThread::UI);
250 void EntryUpdatePerformer::UpdateEntry(const std::string& local_id,
251 const ClientContext& context,
252 const FileOperationCallback& callback) {
253 DCHECK_CURRENTLY_ON(BrowserThread::UI);
254 DCHECK(!callback.is_null());
256 scoped_ptr<LocalState> local_state(new LocalState);
257 LocalState* const local_state_ptr = local_state.get();
258 base::PostTaskAndReplyWithResult(
259 blocking_task_runner_.get(),
260 FROM_HERE,
261 base::Bind(&PrepareUpdate, metadata_, cache_, local_id, local_state_ptr),
262 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare,
263 weak_ptr_factory_.GetWeakPtr(), context, callback,
264 base::Passed(&local_state)));
267 void EntryUpdatePerformer::UpdateEntryAfterPrepare(
268 const ClientContext& context,
269 const FileOperationCallback& callback,
270 scoped_ptr<LocalState> local_state,
271 FileError error) {
272 DCHECK_CURRENTLY_ON(BrowserThread::UI);
273 DCHECK(!callback.is_null());
275 if (error != FILE_ERROR_OK) {
276 callback.Run(error);
277 return;
280 // Trashed entry should be removed.
281 if (local_state->entry.parent_local_id() == util::kDriveTrashDirLocalId) {
282 remove_performer_->Remove(local_state->entry.local_id(), context, callback);
283 return;
286 // Parent was locally created and needs update. Just return for now.
287 // This entry should be updated again after the parent update completes.
288 if (local_state->parent_entry.resource_id().empty() &&
289 local_state->parent_entry.metadata_edit_state() != ResourceEntry::CLEAN) {
290 callback.Run(FILE_ERROR_OK);
291 return;
294 base::Time last_modified = base::Time::FromInternalValue(
295 local_state->entry.file_info().last_modified());
296 base::Time last_accessed = base::Time::FromInternalValue(
297 local_state->entry.file_info().last_accessed());
299 // Compose a list of new properties from the proto.
300 google_apis::drive::Properties properties;
301 for (const auto& proto_property : local_state->entry.new_properties()) {
302 google_apis::drive::Property property;
303 switch (proto_property.visibility()) {
304 case Property_Visibility_PRIVATE:
305 property.set_visibility(
306 google_apis::drive::Property::VISIBILITY_PRIVATE);
307 break;
308 case Property_Visibility_PUBLIC:
309 property.set_visibility(
310 google_apis::drive::Property::VISIBILITY_PUBLIC);
311 break;
313 property.set_key(proto_property.key());
314 property.set_value(proto_property.value());
315 properties.push_back(property);
318 // Perform content update.
319 if (local_state->should_content_update) {
320 if (local_state->entry.resource_id().empty()) {
321 // Not locking the loader intentionally here to avoid making the UI
322 // unresponsive while uploading large files.
323 // FinishUpdate() is responsible to resolve conflicts caused by this.
324 scoped_ptr<base::ScopedClosureRunner> null_loader_lock;
326 UploadNewFileOptions options;
327 options.modified_date = last_modified;
328 options.last_viewed_by_me_date = last_accessed;
329 options.properties = properties;
330 LocalState* const local_state_ptr = local_state.get();
331 scheduler_->UploadNewFile(
332 local_state_ptr->parent_entry.resource_id(),
333 local_state_ptr->drive_file_path, local_state_ptr->cache_file_path,
334 local_state_ptr->entry.title(),
335 local_state_ptr->entry.file_specific_info().content_mime_type(),
336 options, context,
337 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
338 weak_ptr_factory_.GetWeakPtr(), context, callback,
339 base::Passed(&local_state),
340 base::Passed(&null_loader_lock)));
341 } else {
342 UploadExistingFileOptions options;
343 options.title = local_state->entry.title();
344 options.parent_resource_id = local_state->parent_entry.resource_id();
345 options.modified_date = last_modified;
346 options.last_viewed_by_me_date = last_accessed;
347 options.properties = properties;
348 LocalState* const local_state_ptr = local_state.get();
349 scheduler_->UploadExistingFile(
350 local_state_ptr->entry.resource_id(),
351 local_state_ptr->drive_file_path, local_state_ptr->cache_file_path,
352 local_state_ptr->entry.file_specific_info().content_mime_type(),
353 options, context,
354 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
355 weak_ptr_factory_.GetWeakPtr(), context, callback,
356 base::Passed(&local_state),
357 base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
359 return;
362 // Create directory.
363 if (local_state->entry.file_info().is_directory() &&
364 local_state->entry.resource_id().empty()) {
365 // Lock the loader to avoid race conditions.
366 scoped_ptr<base::ScopedClosureRunner> loader_lock =
367 loader_controller_->GetLock();
369 AddNewDirectoryOptions options;
370 options.modified_date = last_modified;
371 options.last_viewed_by_me_date = last_accessed;
372 options.properties = properties;
373 LocalState* const local_state_ptr = local_state.get();
374 scheduler_->AddNewDirectory(
375 local_state_ptr->parent_entry.resource_id(),
376 local_state_ptr->entry.title(), options, context,
377 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
378 weak_ptr_factory_.GetWeakPtr(), context, callback,
379 base::Passed(&local_state), base::Passed(&loader_lock)));
380 return;
383 // No need to perform update.
384 if (local_state->entry.metadata_edit_state() == ResourceEntry::CLEAN ||
385 local_state->entry.resource_id().empty()) {
386 callback.Run(FILE_ERROR_OK);
387 return;
390 // Perform metadata update.
391 LocalState* const local_state_ptr = local_state.get();
392 scheduler_->UpdateResource(
393 local_state_ptr->entry.resource_id(),
394 local_state_ptr->parent_entry.resource_id(),
395 local_state_ptr->entry.title(), last_modified, last_accessed, properties,
396 context,
397 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
398 weak_ptr_factory_.GetWeakPtr(), context, callback,
399 base::Passed(&local_state),
400 base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
403 void EntryUpdatePerformer::UpdateEntryAfterUpdateResource(
404 const ClientContext& context,
405 const FileOperationCallback& callback,
406 scoped_ptr<LocalState> local_state,
407 scoped_ptr<base::ScopedClosureRunner> loader_lock,
408 google_apis::DriveApiErrorCode status,
409 scoped_ptr<google_apis::FileResource> entry) {
410 DCHECK_CURRENTLY_ON(BrowserThread::UI);
411 DCHECK(!callback.is_null());
413 if (status == google_apis::HTTP_FORBIDDEN) {
414 // Editing this entry is not allowed, revert local changes.
415 entry_revert_performer_->RevertEntry(local_state->entry.local_id(), context,
416 callback);
417 return;
420 FileError error = GDataToFileError(status);
421 if (error != FILE_ERROR_OK) {
422 callback.Run(error);
423 return;
426 FileChange* changed_files = new FileChange;
427 base::PostTaskAndReplyWithResult(
428 blocking_task_runner_.get(), FROM_HERE,
429 base::Bind(&FinishUpdate, metadata_, cache_, base::Passed(&local_state),
430 base::Passed(&entry), changed_files),
431 base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
432 weak_ptr_factory_.GetWeakPtr(), callback,
433 base::Owned(changed_files)));
436 void EntryUpdatePerformer::UpdateEntryAfterFinish(
437 const FileOperationCallback& callback,
438 const FileChange* changed_files,
439 FileError error) {
440 DCHECK_CURRENTLY_ON(BrowserThread::UI);
441 DCHECK(!callback.is_null());
443 delegate_->OnFileChangedByOperation(*changed_files);
444 callback.Run(error);
447 } // namespace internal
448 } // namespace drive