local_to_remote_syncer.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/sync_file_system/drive_backend/local_to_remote_syncer.h"
6
7#include <string>
8#include <vector>
9
10#include "base/callback.h"
11#include "base/location.h"
12#include "base/logging.h"
13#include "base/sequenced_task_runner.h"
14#include "base/task_runner_util.h"
15#include "chrome/browser/drive/drive_api_util.h"
16#include "chrome/browser/drive/drive_service_interface.h"
17#include "chrome/browser/drive/drive_uploader.h"
18#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h"
19#include "chrome/browser/sync_file_system/drive_backend/folder_creator.h"
20#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
21#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h"
22#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
23#include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h"
24#include "google_apis/drive/drive_api_parser.h"
25#include "webkit/common/fileapi/file_system_util.h"
26
27namespace sync_file_system {
28namespace drive_backend {
29
30namespace {
31
32scoped_ptr<FileTracker> FindTrackerByID(MetadataDatabase* metadata_database,
33                                        int64 tracker_id) {
34  scoped_ptr<FileTracker> tracker(new FileTracker);
35  if (metadata_database->FindTrackerByTrackerID(tracker_id, tracker.get()))
36    return tracker.Pass();
37  return scoped_ptr<FileTracker>();
38}
39
40void ReturnRetryOnSuccess(const SyncStatusCallback& callback,
41                          SyncStatusCode status) {
42  if (status == SYNC_STATUS_OK)
43    status = SYNC_STATUS_RETRY;
44  callback.Run(status);
45}
46
47}  // namespace
48
49LocalToRemoteSyncer::LocalToRemoteSyncer(SyncEngineContext* sync_context,
50                                         const SyncFileMetadata& local_metadata,
51                                         const FileChange& local_change,
52                                         const base::FilePath& local_path,
53                                         const fileapi::FileSystemURL& url)
54    : sync_context_(sync_context),
55      local_metadata_(local_metadata),
56      local_change_(local_change),
57      local_path_(local_path),
58      url_(url),
59      sync_action_(SYNC_ACTION_NONE),
60      weak_ptr_factory_(this) {
61}
62
63LocalToRemoteSyncer::~LocalToRemoteSyncer() {
64}
65
66void LocalToRemoteSyncer::Run(const SyncStatusCallback& callback) {
67  if (!IsContextReady()) {
68    NOTREACHED();
69    callback.Run(SYNC_STATUS_FAILED);
70    return;
71  }
72
73  SyncStatusCallback wrapped_callback = base::Bind(
74      &LocalToRemoteSyncer::SyncCompleted, weak_ptr_factory_.GetWeakPtr(),
75      callback);
76
77  std::string app_id = url_.origin().host();
78  base::FilePath path = url_.path();
79
80  scoped_ptr<FileTracker> active_ancestor_tracker(new FileTracker);
81  base::FilePath active_ancestor_path;
82  if (!metadata_database()->FindNearestActiveAncestor(
83          app_id, path,
84          active_ancestor_tracker.get(), &active_ancestor_path)) {
85    // The app is disabled or not registered.
86    callback.Run(SYNC_STATUS_UNKNOWN_ORIGIN);
87    return;
88  }
89  DCHECK(active_ancestor_tracker->active());
90  DCHECK(active_ancestor_tracker->has_synced_details());
91  const FileDetails& active_ancestor_details =
92      active_ancestor_tracker->synced_details();
93
94  // TODO(tzik): Consider handling
95  // active_ancestor_tracker->synced_details().missing() case.
96
97  DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE ||
98         active_ancestor_details.file_kind() == FILE_KIND_FOLDER);
99
100  base::FilePath missing_entries;
101  if (active_ancestor_path.empty()) {
102    missing_entries = path;
103  } else if (active_ancestor_path != path) {
104    bool should_success = active_ancestor_path.AppendRelativePath(
105        path, &missing_entries);
106    if (!should_success) {
107      NOTREACHED();
108      callback.Run(SYNC_STATUS_FAILED);
109      return;
110    }
111  }
112
113  std::vector<base::FilePath::StringType> missing_components;
114  fileapi::VirtualPath::GetComponents(missing_entries, &missing_components);
115
116  if (!missing_components.empty()) {
117    if (local_change_.IsDelete() ||
118        local_metadata_.file_type == SYNC_FILE_TYPE_UNKNOWN) {
119      // !IsDelete() but SYNC_FILE_TYPE_UNKNOWN could happen when a file is
120      // deleted by recursive deletion (which is not recorded by tracker)
121      // but there're remaining changes for the same file in the tracker.
122
123      // Local file is deleted and remote file is missing, already deleted or
124      // not yet synced.  There is nothing to do for the file.
125      callback.Run(SYNC_STATUS_OK);
126      return;
127    }
128  }
129
130  if (missing_components.size() > 1) {
131    // The original target doesn't have remote file and parent.
132    // Try creating the parent first.
133    if (active_ancestor_details.file_kind() == FILE_KIND_FOLDER) {
134      remote_parent_folder_tracker_ = active_ancestor_tracker.Pass();
135      target_path_ = active_ancestor_path.Append(missing_components[0]);
136      CreateRemoteFolder(wrapped_callback);
137      return;
138    }
139
140    DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE);
141    remote_parent_folder_tracker_ =
142        FindTrackerByID(metadata_database(),
143                        active_ancestor_tracker->parent_tracker_id());
144    remote_file_tracker_ = active_ancestor_tracker.Pass();
145    target_path_ = active_ancestor_path;
146    DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForCreateFolder,
147                                weak_ptr_factory_.GetWeakPtr(),
148                                wrapped_callback));
149
150    return;
151  }
152
153  if (missing_components.empty()) {
154    // The original target has remote active file/folder.
155    remote_parent_folder_tracker_ =
156        FindTrackerByID(metadata_database(),
157                        active_ancestor_tracker->parent_tracker_id());
158    remote_file_tracker_ = active_ancestor_tracker.Pass();
159    target_path_ = url_.path();
160    DCHECK(target_path_ == active_ancestor_path);
161
162    if (remote_file_tracker_->dirty()) {
163      // Both local and remote file has pending modification.
164      HandleConflict(wrapped_callback);
165      return;
166    }
167
168    // Non-conflicting file/folder update case.
169    HandleExistingRemoteFile(wrapped_callback);
170    return;
171  }
172
173  DCHECK(local_change_.IsAddOrUpdate());
174  DCHECK_EQ(1u, missing_components.size());
175  // The original target has remote parent folder and doesn't have remote active
176  // file.
177  // Upload the file as a new file or create a folder.
178  remote_parent_folder_tracker_ = active_ancestor_tracker.Pass();
179  target_path_ = url_.path();
180  DCHECK(target_path_ == active_ancestor_path.Append(missing_components[0]));
181  if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) {
182    UploadNewFile(wrapped_callback);
183    return;
184  }
185  CreateRemoteFolder(wrapped_callback);
186}
187
188void LocalToRemoteSyncer::SyncCompleted(const SyncStatusCallback& callback,
189                                        SyncStatusCode status) {
190  if (status == SYNC_STATUS_OK && target_path_ != url_.path()) {
191    callback.Run(SYNC_STATUS_RETRY);
192    return;
193  }
194
195  callback.Run(status);
196}
197
198void LocalToRemoteSyncer::HandleConflict(const SyncStatusCallback& callback) {
199  DCHECK(remote_file_tracker_);
200  DCHECK(remote_file_tracker_->has_synced_details());
201  DCHECK(remote_file_tracker_->active());
202  DCHECK(remote_file_tracker_->dirty());
203
204  if (local_change_.IsFile()) {
205    UploadNewFile(callback);
206    return;
207  }
208
209  DCHECK(local_change_.IsDirectory());
210  // Check if we can reuse the remote folder.
211  FileMetadata remote_file_metadata;
212  bool should_success = metadata_database()->FindFileByFileID(
213      remote_file_tracker_->file_id(), &remote_file_metadata);
214  if (!should_success) {
215    NOTREACHED();
216    CreateRemoteFolder(callback);
217    return;
218  }
219
220  const FileDetails& remote_details = remote_file_metadata.details();
221  base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
222  if (!remote_details.missing() &&
223      remote_details.file_kind() == FILE_KIND_FOLDER &&
224      remote_details.title() == title.AsUTF8Unsafe() &&
225      HasFileAsParent(remote_details,
226                      remote_parent_folder_tracker_->file_id())) {
227    metadata_database()->UpdateTracker(
228        remote_file_tracker_->tracker_id(), remote_details, callback);
229    return;
230  }
231
232  // Create new remote folder.
233  CreateRemoteFolder(callback);
234}
235
236void LocalToRemoteSyncer::HandleExistingRemoteFile(
237    const SyncStatusCallback& callback) {
238  DCHECK(remote_file_tracker_);
239  DCHECK(!remote_file_tracker_->dirty());
240  DCHECK(remote_file_tracker_->active());
241  DCHECK(remote_file_tracker_->has_synced_details());
242
243  if (local_change_.IsDelete() ||
244      local_metadata_.file_type == SYNC_FILE_TYPE_UNKNOWN) {
245    // Local file deletion for existing remote file.
246    DeleteRemoteFile(callback);
247    return;
248  }
249
250  DCHECK(local_change_.IsAddOrUpdate());
251  DCHECK(local_change_.file_type() == SYNC_FILE_TYPE_FILE ||
252         local_change_.file_type() == SYNC_FILE_TYPE_DIRECTORY);
253
254  const FileDetails& synced_details = remote_file_tracker_->synced_details();
255  DCHECK(synced_details.file_kind() == FILE_KIND_FILE ||
256         synced_details.file_kind() == FILE_KIND_FOLDER);
257  if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) {
258    if (synced_details.file_kind() == FILE_KIND_FILE) {
259      // Non-conflicting local file update to existing remote regular file.
260      UploadExistingFile(callback);
261      return;
262    }
263
264    DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind());
265    // Non-conflicting local file update to existing remote *folder*.
266    // Assuming this case as local folder deletion + local file creation, delete
267    // the remote folder and upload the file.
268    DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForUploadNewFile,
269                                weak_ptr_factory_.GetWeakPtr(),
270                                callback));
271    return;
272  }
273
274  DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_change_.file_type());
275  if (synced_details.file_kind() == FILE_KIND_FILE) {
276    // Non-conflicting local folder creation to existing remote *file*.
277    // Assuming this case as local file deletion + local folder creation, delete
278    // the remote file and create a remote folder.
279    DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForCreateFolder,
280                                weak_ptr_factory_.GetWeakPtr(), callback));
281    return;
282  }
283
284  // Non-conflicting local folder creation to existing remote folder.
285  DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind());
286  callback.Run(SYNC_STATUS_OK);
287}
288
289void LocalToRemoteSyncer::DeleteRemoteFile(
290    const SyncStatusCallback& callback) {
291  DCHECK(remote_file_tracker_);
292  DCHECK(remote_file_tracker_->has_synced_details());
293
294  sync_action_ = SYNC_ACTION_DELETED;
295  drive_service()->DeleteResource(
296      remote_file_tracker_->file_id(),
297      remote_file_tracker_->synced_details().etag(),
298      base::Bind(&LocalToRemoteSyncer::DidDeleteRemoteFile,
299                 weak_ptr_factory_.GetWeakPtr(),
300                 callback));
301}
302
303void LocalToRemoteSyncer::DidDeleteRemoteFile(
304    const SyncStatusCallback& callback,
305    google_apis::GDataErrorCode error) {
306  if (error != google_apis::HTTP_SUCCESS &&
307      error != google_apis::HTTP_NOT_FOUND &&
308      error != google_apis::HTTP_PRECONDITION &&
309      error != google_apis::HTTP_CONFLICT) {
310    callback.Run(GDataErrorCodeToSyncStatusCode(error));
311    return;
312  }
313
314  // Handle NOT_FOUND case as SUCCESS case.
315  // For PRECONDITION / CONFLICT case, the remote file is modified since the
316  // last sync completed.  As our policy for deletion-modification conflict
317  // resolution, ignore the local deletion.
318  callback.Run(SYNC_STATUS_OK);
319}
320
321void LocalToRemoteSyncer::UploadExistingFile(
322    const SyncStatusCallback& callback)  {
323  DCHECK(remote_file_tracker_);
324  DCHECK(remote_file_tracker_->has_synced_details());
325
326  base::PostTaskAndReplyWithResult(
327      sync_context_->GetBlockingTaskRunner(), FROM_HERE,
328      base::Bind(&drive::util::GetMd5Digest, local_path_),
329      base::Bind(&LocalToRemoteSyncer::DidGetMD5ForUpload,
330                 weak_ptr_factory_.GetWeakPtr(),
331                 callback));
332}
333
334void LocalToRemoteSyncer::DidGetMD5ForUpload(
335    const SyncStatusCallback& callback,
336    const std::string& local_file_md5) {
337  if (local_file_md5 == remote_file_tracker_->synced_details().md5()) {
338    // Local file is not changed.
339    callback.Run(SYNC_STATUS_OK);
340    return;
341  }
342
343  sync_action_ = SYNC_ACTION_UPDATED;
344  drive_uploader()->UploadExistingFile(
345      remote_file_tracker_->file_id(),
346      local_path_,
347      "application/octet_stream",
348      remote_file_tracker_->synced_details().etag(),
349      base::Bind(&LocalToRemoteSyncer::DidUploadExistingFile,
350                 weak_ptr_factory_.GetWeakPtr(),
351                 callback),
352      google_apis::ProgressCallback());
353}
354
355void LocalToRemoteSyncer::DidUploadExistingFile(
356    const SyncStatusCallback& callback,
357    google_apis::GDataErrorCode error,
358    const GURL&,
359    scoped_ptr<google_apis::ResourceEntry> entry) {
360  if (error == google_apis::HTTP_PRECONDITION ||
361      error == google_apis::HTTP_CONFLICT) {
362    // The remote file has unfetched remote change.  Fetch latest metadata and
363    // update database with it.
364    // TODO(tzik): Consider adding local side low-priority dirtiness handling to
365    // handle this as ListChangesTask.
366    UpdateRemoteMetadata(remote_file_tracker_->file_id(),
367                         base::Bind(&ReturnRetryOnSuccess, callback));
368    return;
369  }
370
371  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
372  if (status != SYNC_STATUS_OK) {
373    callback.Run(status);
374    return;
375  }
376
377  if (!entry) {
378    NOTREACHED();
379    callback.Run(SYNC_STATUS_FAILED);
380    return;
381  }
382
383  DCHECK(entry);
384  metadata_database()->UpdateByFileResource(
385      *drive::util::ConvertResourceEntryToFileResource(*entry),
386      base::Bind(&LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile,
387                 weak_ptr_factory_.GetWeakPtr(),
388                 callback));
389}
390
391void LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile(
392    const SyncStatusCallback& callback,
393    SyncStatusCode status) {
394  if (status != SYNC_STATUS_OK) {
395    callback.Run(status);
396    return;
397  }
398
399  FileMetadata file;
400  bool should_success = metadata_database()->FindFileByFileID(
401      remote_file_tracker_->file_id(), &file);
402  if (!should_success) {
403    NOTREACHED();
404    callback.Run(SYNC_STATUS_FAILED);
405    return;
406  }
407
408  const FileDetails& details = file.details();
409  base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
410  if (!details.missing() &&
411      details.file_kind() == FILE_KIND_FILE &&
412      details.title() == title.AsUTF8Unsafe() &&
413      HasFileAsParent(details,
414                      remote_parent_folder_tracker_->file_id())) {
415    metadata_database()->UpdateTracker(
416        remote_file_tracker_->tracker_id(),
417        file.details(),
418        callback);
419    return;
420  }
421
422  callback.Run(SYNC_STATUS_RETRY);
423}
424
425void LocalToRemoteSyncer::UpdateRemoteMetadata(
426    const std::string& file_id,
427    const SyncStatusCallback& callback) {
428  DCHECK(remote_file_tracker_);
429  drive_service()->GetResourceEntry(
430      file_id,
431      base::Bind(&LocalToRemoteSyncer::DidGetRemoteMetadata,
432                 weak_ptr_factory_.GetWeakPtr(),
433                 file_id, callback));
434}
435
436void LocalToRemoteSyncer::DidGetRemoteMetadata(
437    const std::string& file_id,
438    const SyncStatusCallback& callback,
439    google_apis::GDataErrorCode error,
440    scoped_ptr<google_apis::ResourceEntry> entry) {
441  if (error == google_apis::HTTP_NOT_FOUND) {
442    metadata_database()->UpdateByDeletedRemoteFile(file_id, callback);
443    return;
444  }
445
446  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
447  if (status != SYNC_STATUS_OK) {
448    callback.Run(status);
449    return;
450  }
451
452  if (!entry) {
453    NOTREACHED();
454    callback.Run(SYNC_STATUS_FAILED);
455    return;
456  }
457
458  metadata_database()->UpdateByFileResource(
459      *drive::util::ConvertResourceEntryToFileResource(*entry), callback);
460}
461
462void LocalToRemoteSyncer::DidDeleteForUploadNewFile(
463    const SyncStatusCallback& callback,
464    SyncStatusCode status) {
465  if (status == SYNC_STATUS_HAS_CONFLICT) {
466    UpdateRemoteMetadata(remote_file_tracker_->file_id(),
467                         base::Bind(&ReturnRetryOnSuccess, callback));
468    return;
469  }
470
471  if (status != SYNC_STATUS_OK) {
472    callback.Run(status);
473    return;
474  }
475
476  UploadNewFile(callback);
477}
478
479void LocalToRemoteSyncer::DidDeleteForCreateFolder(
480    const SyncStatusCallback& callback,
481    SyncStatusCode status) {
482  if (status == SYNC_STATUS_HAS_CONFLICT) {
483    UpdateRemoteMetadata(remote_file_tracker_->file_id(),
484                         base::Bind(&ReturnRetryOnSuccess, callback));
485    return;
486  }
487
488  if (status != SYNC_STATUS_OK) {
489    callback.Run(status);
490    return;
491  }
492
493  CreateRemoteFolder(callback);
494}
495
496void LocalToRemoteSyncer::UploadNewFile(const SyncStatusCallback& callback) {
497  DCHECK(remote_parent_folder_tracker_);
498
499  sync_action_ = SYNC_ACTION_ADDED;
500  base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
501  drive_uploader()->UploadNewFile(
502      remote_parent_folder_tracker_->file_id(),
503      local_path_,
504      title.AsUTF8Unsafe(),
505      GetMimeTypeFromTitle(title),
506      base::Bind(&LocalToRemoteSyncer::DidUploadNewFile,
507                 weak_ptr_factory_.GetWeakPtr(),
508                 callback),
509      google_apis::ProgressCallback());
510}
511
512void LocalToRemoteSyncer::DidUploadNewFile(
513    const SyncStatusCallback& callback,
514    google_apis::GDataErrorCode error,
515    const GURL& upload_location,
516    scoped_ptr<google_apis::ResourceEntry> entry) {
517  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
518  if (status != SYNC_STATUS_OK) {
519    callback.Run(status);
520    return;
521  }
522
523  if (!entry) {
524    NOTREACHED();
525    callback.Run(SYNC_STATUS_FAILED);
526    return;
527  }
528
529  metadata_database()->ReplaceActiveTrackerWithNewResource(
530      remote_parent_folder_tracker_->tracker_id(),
531      *drive::util::ConvertResourceEntryToFileResource(*entry),
532      callback);
533}
534
535void LocalToRemoteSyncer::CreateRemoteFolder(
536    const SyncStatusCallback& callback) {
537  DCHECK(remote_parent_folder_tracker_);
538
539  base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
540  sync_action_ = SYNC_ACTION_ADDED;
541
542  DCHECK(!folder_creator_);
543  folder_creator_.reset(new FolderCreator(
544      drive_service(), metadata_database(),
545      remote_parent_folder_tracker_->file_id(),
546      title.AsUTF8Unsafe()));
547  folder_creator_->Run(base::Bind(
548      &LocalToRemoteSyncer::DidCreateRemoteFolder,
549      weak_ptr_factory_.GetWeakPtr(),
550      callback));
551}
552
553void LocalToRemoteSyncer::DidCreateRemoteFolder(
554    const SyncStatusCallback& callback,
555    const std::string& file_id,
556    SyncStatusCode status) {
557  scoped_ptr<FolderCreator> deleter = folder_creator_.Pass();
558  if (status != SYNC_STATUS_OK) {
559    callback.Run(status);
560    return;
561  }
562
563  if (metadata_database()->TryNoSideEffectActivation(
564          remote_parent_folder_tracker_->tracker_id(),
565          file_id, callback)) {
566    // |callback| will be invoked by MetadataDatabase in this case.
567    return;
568  }
569
570  drive_service()->RemoveResourceFromDirectory(
571      remote_parent_folder_tracker_->file_id(), file_id,
572      base::Bind(&LocalToRemoteSyncer::DidDetachResourceForCreationConflict,
573                 weak_ptr_factory_.GetWeakPtr(),
574                 callback));
575}
576
577void LocalToRemoteSyncer::DidDetachResourceForCreationConflict(
578    const SyncStatusCallback& callback,
579    google_apis::GDataErrorCode error) {
580  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
581  if (status != SYNC_STATUS_OK) {
582    callback.Run(status);
583    return;
584  }
585
586  callback.Run(SYNC_STATUS_RETRY);
587}
588
589bool LocalToRemoteSyncer::IsContextReady() {
590  return sync_context_->GetDriveService() &&
591      sync_context_->GetDriveUploader() &&
592      sync_context_->GetMetadataDatabase();
593}
594
595drive::DriveServiceInterface* LocalToRemoteSyncer::drive_service() {
596  set_used_network(true);
597  return sync_context_->GetDriveService();
598}
599
600drive::DriveUploaderInterface* LocalToRemoteSyncer::drive_uploader() {
601  set_used_network(true);
602  return sync_context_->GetDriveUploader();
603}
604
605MetadataDatabase* LocalToRemoteSyncer::metadata_database() {
606  return sync_context_->GetMetadataDatabase();
607}
608
609}  // namespace drive_backend
610}  // namespace sync_file_system
611