Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / sync / entry_update_performer.cc
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
20 using content::BrowserThread;
21
22 namespace drive {
23 namespace internal {
24
25 struct EntryUpdatePerformer::LocalState {
26   LocalState() : should_content_update(false) {
27   }
28
29   ResourceEntry entry;
30   ResourceEntry parent_entry;
31   base::FilePath drive_file_path;
32   base::FilePath cache_file_path;
33   bool should_content_update;
34 };
35
36 namespace {
37
38 // Looks up ResourceEntry for source entry and its parent.
39 FileError PrepareUpdate(ResourceMetadata* metadata,
40                         FileCache* cache,
41                         const std::string& local_id,
42                         EntryUpdatePerformer::LocalState* local_state) {
43   FileError error = metadata->GetResourceEntryById(local_id,
44                                                    &local_state->entry);
45   if (error != FILE_ERROR_OK)
46     return error;
47
48   error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
49                                          &local_state->parent_entry);
50   if (error != FILE_ERROR_OK)
51     return error;
52
53   local_state->drive_file_path = metadata->GetFilePath(local_id);
54   if (local_state->drive_file_path.empty())
55     return FILE_ERROR_NOT_FOUND;
56
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)
68       return error;
69     if (!cache->GetCacheEntry(local_id, &cache_entry))
70       return FILE_ERROR_NOT_FOUND;
71   }
72
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)
79         return error;
80       if (!cache->GetCacheEntry(local_id, &cache_entry))
81         return FILE_ERROR_NOT_FOUND;
82     }
83
84     if (cache_entry.md5() == local_state->entry.file_specific_info().md5()) {
85       error = cache->ClearDirty(local_id);
86       if (error != FILE_ERROR_OK)
87         return error;
88     } else {
89       error = cache->GetFile(local_id, &local_state->cache_file_path);
90       if (error != FILE_ERROR_OK)
91         return error;
92
93       local_state->should_content_update = true;
94     }
95   }
96
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.
101       break;
102
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)
107         return error;
108       break;
109   }
110   return FILE_ERROR_OK;
111 }
112
113 FileError FinishUpdate(ResourceMetadata* metadata,
114                        FileCache* cache,
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);
123   switch (error) {
124     case FILE_ERROR_OK:
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)
130           return error;
131         *changed_directory = existing_entry_path.DirName();
132       }
133       break;
134     case FILE_ERROR_NOT_FOUND:
135       break;
136     default:
137       return error;
138   }
139
140   ResourceEntry entry;
141   error = metadata->GetResourceEntryById(local_id, &entry);
142   if (error != FILE_ERROR_OK)
143     return error;
144
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.
149       break;
150
151     case ResourceEntry::SYNCING:
152       entry.set_metadata_edit_state(ResourceEntry::CLEAN);
153       break;
154   }
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)
160     return error;
161
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)
169       return error;
170   }
171   return FILE_ERROR_OK;
172 }
173
174 }  // namespace
175
176 EntryUpdatePerformer::EntryUpdatePerformer(
177     base::SequencedTaskRunner* blocking_task_runner,
178     file_system::OperationObserver* observer,
179     JobScheduler* scheduler,
180     ResourceMetadata* metadata,
181     FileCache* cache,
182     LoaderController* loader_controller)
183     : blocking_task_runner_(blocking_task_runner),
184       observer_(observer),
185       scheduler_(scheduler),
186       metadata_(metadata),
187       cache_(cache),
188       loader_controller_(loader_controller),
189       remove_performer_(new RemovePerformer(blocking_task_runner,
190                                             observer,
191                                             scheduler,
192                                             metadata)),
193       entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
194                                                        observer,
195                                                        scheduler,
196                                                        metadata)),
197       weak_ptr_factory_(this) {
198   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
199 }
200
201 EntryUpdatePerformer::~EntryUpdatePerformer() {
202   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
203 }
204
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());
210
211   scoped_ptr<LocalState> local_state(new LocalState);
212   LocalState* local_state_ptr = local_state.get();
213   base::PostTaskAndReplyWithResult(
214       blocking_task_runner_.get(),
215       FROM_HERE,
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)));
220 }
221
222 void EntryUpdatePerformer::UpdateEntryAfterPrepare(
223     const ClientContext& context,
224     const FileOperationCallback& callback,
225     scoped_ptr<LocalState> local_state,
226     FileError error) {
227   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
228   DCHECK(!callback.is_null());
229
230   if (error != FILE_ERROR_OK) {
231     callback.Run(error);
232     return;
233   }
234
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);
238     return;
239   }
240
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);
246     return;
247   }
248
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());
253
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;
261
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(),
271           options,
272           context,
273           base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
274                      weak_ptr_factory_.GetWeakPtr(),
275                      context,
276                      callback,
277                      local_state->entry.local_id(),
278                      base::Passed(&null_loader_lock)));
279     } else {
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(),
290           options,
291           context,
292           base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
293                      weak_ptr_factory_.GetWeakPtr(),
294                      context,
295                      callback,
296                      local_state->entry.local_id(),
297                      base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
298     }
299     return;
300   }
301
302   // Create directory.
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();
308
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(),
315         options,
316         context,
317         base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
318                    weak_ptr_factory_.GetWeakPtr(),
319                    context,
320                    callback,
321                    local_state->entry.local_id(),
322                    base::Passed(&loader_lock)));
323     return;
324   }
325
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);
330     return;
331   }
332
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,
337       context,
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>())));
342 }
343
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());
353
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);
357     return;
358   }
359
360   FileError error = GDataToFileError(status);
361   if (error != FILE_ERROR_OK) {
362     callback.Run(error);
363     return;
364   }
365
366   base::FilePath* changed_directory = new base::FilePath;
367   base::PostTaskAndReplyWithResult(
368       blocking_task_runner_.get(),
369       FROM_HERE,
370       base::Bind(&FinishUpdate,
371                  metadata_, cache_, local_id, base::Passed(&resource_entry),
372                  changed_directory),
373       base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
374                  weak_ptr_factory_.GetWeakPtr(), callback,
375                  base::Owned(changed_directory)));
376 }
377
378 void EntryUpdatePerformer::UpdateEntryAfterFinish(
379     const FileOperationCallback& callback,
380     const base::FilePath* changed_directory,
381     FileError error) {
382   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
383   DCHECK(!callback.is_null());
384
385   if (!changed_directory->empty())
386     observer_->OnDirectoryChangedByOperation(*changed_directory);
387   callback.Run(error);
388 }
389
390 }  // namespace internal
391 }  // namespace drive