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