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/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"
21
22using content::BrowserThread;
23
24namespace drive {
25namespace internal {
26
27struct EntryUpdatePerformer::LocalState {
28  LocalState() : should_content_update(false) {
29  }
30
31  ResourceEntry entry;
32  ResourceEntry parent_entry;
33  base::FilePath drive_file_path;
34  base::FilePath cache_file_path;
35  bool should_content_update;
36};
37
38namespace {
39
40// Looks up ResourceEntry for source entry and its parent.
41FileError PrepareUpdate(ResourceMetadata* metadata,
42                        FileCache* cache,
43                        const std::string& local_id,
44                        EntryUpdatePerformer::LocalState* local_state) {
45  FileError error = metadata->GetResourceEntryById(local_id,
46                                                   &local_state->entry);
47  if (error != FILE_ERROR_OK)
48    return error;
49
50  error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
51                                         &local_state->parent_entry);
52  if (error != FILE_ERROR_OK)
53    return error;
54
55  error = metadata->GetFilePath(local_id, &local_state->drive_file_path);
56  if (error != FILE_ERROR_OK)
57    return error;
58
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)
69      return error;
70    error = metadata->GetResourceEntryById(local_id, &local_state->entry);
71    if (error != FILE_ERROR_OK)
72      return error;
73  }
74
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)
82        return error;
83      error = metadata->GetResourceEntryById(local_id, &local_state->entry);
84      if (error != FILE_ERROR_OK)
85        return error;
86    }
87
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)
92        return error;
93    } else {
94      error = cache->GetFile(local_id, &local_state->cache_file_path);
95      if (error != FILE_ERROR_OK)
96        return error;
97
98      local_state->should_content_update = true;
99    }
100  }
101
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.
106      break;
107
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)
112        return error;
113      break;
114  }
115  return FILE_ERROR_OK;
116}
117
118FileError FinishUpdate(ResourceMetadata* metadata,
119                       FileCache* cache,
120                       const std::string& local_id,
121                       scoped_ptr<google_apis::FileResource> file_resource,
122                       FileChange* changed_files) {
123  ResourceEntry entry;
124  FileError error = metadata->GetResourceEntryById(local_id, &entry);
125  if (error != FILE_ERROR_OK)
126    return error;
127
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;
131  error =
132      metadata->GetIdByResourceId(file_resource->file_id(), &existing_local_id);
133
134  switch (error) {
135    case FILE_ERROR_OK:
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)
140          return error;
141        error = metadata->RemoveEntry(existing_local_id);
142        if (error != FILE_ERROR_OK)
143          return error;
144        changed_files->Update(existing_entry_path, entry, FileChange::DELETE);
145      }
146      break;
147    case FILE_ERROR_NOT_FOUND:
148      break;
149    default:
150      return error;
151  }
152
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.
157      break;
158
159    case ResourceEntry::SYNCING:
160      entry.set_metadata_edit_state(ResourceEntry::CLEAN);
161      break;
162  }
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)
168    return error;
169  base::FilePath entry_path;
170  error = metadata->GetFilePath(local_id, &entry_path);
171  if (error != FILE_ERROR_OK)
172    return error;
173  changed_files->Update(entry_path, entry, FileChange::ADD_OR_UPDATE);
174
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)
181      return error;
182  }
183  return FILE_ERROR_OK;
184}
185
186}  // namespace
187
188EntryUpdatePerformer::EntryUpdatePerformer(
189    base::SequencedTaskRunner* blocking_task_runner,
190    file_system::OperationDelegate* delegate,
191    JobScheduler* scheduler,
192    ResourceMetadata* metadata,
193    FileCache* cache,
194    LoaderController* loader_controller)
195    : blocking_task_runner_(blocking_task_runner),
196      delegate_(delegate),
197      scheduler_(scheduler),
198      metadata_(metadata),
199      cache_(cache),
200      loader_controller_(loader_controller),
201      remove_performer_(new RemovePerformer(blocking_task_runner,
202                                            delegate,
203                                            scheduler,
204                                            metadata)),
205      entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
206                                                       delegate,
207                                                       scheduler,
208                                                       metadata)),
209      weak_ptr_factory_(this) {
210  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
211}
212
213EntryUpdatePerformer::~EntryUpdatePerformer() {
214  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
215}
216
217void 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());
222
223  scoped_ptr<LocalState> local_state(new LocalState);
224  LocalState* local_state_ptr = local_state.get();
225  base::PostTaskAndReplyWithResult(
226      blocking_task_runner_.get(),
227      FROM_HERE,
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)));
232}
233
234void EntryUpdatePerformer::UpdateEntryAfterPrepare(
235    const ClientContext& context,
236    const FileOperationCallback& callback,
237    scoped_ptr<LocalState> local_state,
238    FileError error) {
239  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240  DCHECK(!callback.is_null());
241
242  if (error != FILE_ERROR_OK) {
243    callback.Run(error);
244    return;
245  }
246
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);
250    return;
251  }
252
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);
258    return;
259  }
260
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());
265
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;
273
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(),
283          options,
284          context,
285          base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
286                     weak_ptr_factory_.GetWeakPtr(),
287                     context,
288                     callback,
289                     local_state->entry.local_id(),
290                     base::Passed(&null_loader_lock)));
291    } else {
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(),
302          options,
303          context,
304          base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
305                     weak_ptr_factory_.GetWeakPtr(),
306                     context,
307                     callback,
308                     local_state->entry.local_id(),
309                     base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
310    }
311    return;
312  }
313
314  // Create directory.
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();
320
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(),
327        options,
328        context,
329        base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
330                   weak_ptr_factory_.GetWeakPtr(),
331                   context,
332                   callback,
333                   local_state->entry.local_id(),
334                   base::Passed(&loader_lock)));
335    return;
336  }
337
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);
342    return;
343  }
344
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,
349      context,
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>())));
354}
355
356void 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());
365
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);
369    return;
370  }
371
372  FileError error = GDataToFileError(status);
373  if (error != FILE_ERROR_OK) {
374    callback.Run(error);
375    return;
376  }
377
378  FileChange* changed_files = new FileChange;
379  base::PostTaskAndReplyWithResult(
380      blocking_task_runner_.get(),
381      FROM_HERE,
382      base::Bind(&FinishUpdate,
383                 metadata_,
384                 cache_,
385                 local_id,
386                 base::Passed(&entry),
387                 changed_files),
388      base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
389                 weak_ptr_factory_.GetWeakPtr(),
390                 callback,
391                 base::Owned(changed_files)));
392}
393
394void EntryUpdatePerformer::UpdateEntryAfterFinish(
395    const FileOperationCallback& callback,
396    const FileChange* changed_files,
397    FileError error) {
398  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
399  DCHECK(!callback.is_null());
400
401  delegate_->OnFileChangedByOperation(*changed_files);
402  callback.Run(error);
403}
404
405}  // namespace internal
406}  // namespace drive
407