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_v1/local_sync_delegate.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "chrome/browser/sync_file_system/conflict_resolution_resolver.h"
10#include "chrome/browser/sync_file_system/drive_backend_v1/api_util.h"
11#include "chrome/browser/sync_file_system/drive_backend_v1/drive_metadata_store.h"
12#include "chrome/browser/sync_file_system/logger.h"
13#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
14
15namespace sync_file_system {
16namespace drive_backend {
17
18LocalSyncDelegate::LocalSyncDelegate(
19    DriveFileSyncService* sync_service,
20    const FileChange& local_change,
21    const base::FilePath& local_path,
22    const SyncFileMetadata& local_metadata,
23    const fileapi::FileSystemURL& url)
24    : sync_service_(sync_service),
25      operation_(SYNC_OPERATION_NONE),
26      url_(url),
27      local_change_(local_change),
28      local_path_(local_path),
29      local_metadata_(local_metadata),
30      has_drive_metadata_(false),
31      has_remote_change_(false),
32      weak_factory_(this) {}
33
34LocalSyncDelegate::~LocalSyncDelegate() {}
35
36void LocalSyncDelegate::Run(const SyncStatusCallback& callback) {
37  // TODO(nhiroki): support directory operations (http://crbug.com/161442).
38  DCHECK(IsSyncFSDirectoryOperationEnabled() || !local_change_.IsDirectory());
39  operation_ = SYNC_OPERATION_NONE;
40
41  has_drive_metadata_ =
42      metadata_store()->ReadEntry(url_, &drive_metadata_) == SYNC_STATUS_OK;
43
44  if (!has_drive_metadata_)
45    drive_metadata_.set_md5_checksum(std::string());
46
47  sync_service_->EnsureOriginRootDirectory(
48      url_.origin(),
49      base::Bind(&LocalSyncDelegate::DidGetOriginRoot,
50                 weak_factory_.GetWeakPtr(),
51                 callback));
52}
53
54void LocalSyncDelegate::DidGetOriginRoot(
55    const SyncStatusCallback& callback,
56    SyncStatusCode status,
57    const std::string& origin_resource_id) {
58  if (status != SYNC_STATUS_OK) {
59    callback.Run(status);
60    return;
61  }
62
63  origin_resource_id_ = origin_resource_id;
64
65  has_remote_change_ =
66      remote_change_handler()->GetChangeForURL(url_, &remote_change_);
67  if (has_remote_change_ && drive_metadata_.resource_id().empty())
68    drive_metadata_.set_resource_id(remote_change_.resource_id);
69
70  SyncFileType remote_file_type =
71      has_remote_change_ ? remote_change_.change.file_type() :
72      has_drive_metadata_ ?
73          DriveFileSyncService::DriveMetadataResourceTypeToSyncFileType(
74              drive_metadata_.type())
75      : SYNC_FILE_TYPE_UNKNOWN;
76
77  DCHECK_EQ(SYNC_OPERATION_NONE, operation_);
78  operation_ = LocalSyncOperationResolver::Resolve(
79      local_change_,
80      has_remote_change_ ? &remote_change_.change : NULL,
81      has_drive_metadata_ ? &drive_metadata_ : NULL);
82
83  util::Log(logging::LOG_VERBOSE, FROM_HERE,
84            "ApplyLocalChange for %s local_change:%s ===> %s",
85            url_.DebugString().c_str(),
86            local_change_.DebugString().c_str(),
87            SyncOperationTypeToString(operation_));
88
89  switch (operation_) {
90    case SYNC_OPERATION_ADD_FILE:
91      UploadNewFile(callback);
92      return;
93    case SYNC_OPERATION_ADD_DIRECTORY:
94      CreateDirectory(callback);
95      return;
96    case SYNC_OPERATION_UPDATE_FILE:
97      UploadExistingFile(callback);
98      return;
99    case SYNC_OPERATION_DELETE:
100      Delete(callback);
101      return;
102    case SYNC_OPERATION_NONE:
103      callback.Run(SYNC_STATUS_OK);
104      return;
105    case SYNC_OPERATION_CONFLICT:
106      HandleConflict(callback);
107      return;
108    case SYNC_OPERATION_RESOLVE_TO_LOCAL:
109      ResolveToLocal(callback);
110      return;
111    case SYNC_OPERATION_RESOLVE_TO_REMOTE:
112      ResolveToRemote(callback, remote_file_type);
113      return;
114    case SYNC_OPERATION_DELETE_METADATA:
115      DeleteMetadata(base::Bind(
116          &LocalSyncDelegate::DidApplyLocalChange,
117          weak_factory_.GetWeakPtr(), callback, google_apis::HTTP_SUCCESS));
118      return;
119    case SYNC_OPERATION_FAIL: {
120      callback.Run(SYNC_STATUS_FAILED);
121      return;
122    }
123  }
124  NOTREACHED();
125  callback.Run(SYNC_STATUS_FAILED);
126}
127
128void LocalSyncDelegate::UploadNewFile(const SyncStatusCallback& callback) {
129  api_util()->UploadNewFile(
130      origin_resource_id_,
131      local_path_,
132      DriveFileSyncService::PathToTitle(url_.path()),
133      base::Bind(&LocalSyncDelegate::DidUploadNewFile,
134                 weak_factory_.GetWeakPtr(), callback));
135}
136
137void LocalSyncDelegate::DidUploadNewFile(
138    const SyncStatusCallback& callback,
139    google_apis::GDataErrorCode error,
140    const std::string& resource_id,
141    const std::string& md5) {
142  switch (error) {
143    case google_apis::HTTP_CREATED:
144      UpdateMetadata(
145          resource_id, md5, DriveMetadata::RESOURCE_TYPE_FILE,
146          base::Bind(&LocalSyncDelegate::DidApplyLocalChange,
147                     weak_factory_.GetWeakPtr(), callback, error));
148      sync_service_->NotifyObserversFileStatusChanged(
149          url_,
150          SYNC_FILE_STATUS_SYNCED,
151          SYNC_ACTION_ADDED,
152          SYNC_DIRECTION_LOCAL_TO_REMOTE);
153      return;
154    case google_apis::HTTP_CONFLICT:
155      HandleCreationConflict(resource_id, DriveMetadata::RESOURCE_TYPE_FILE,
156                             callback);
157      return;
158    default:
159      callback.Run(GDataErrorCodeToSyncStatusCodeWrapper(error));
160  }
161}
162
163void LocalSyncDelegate::CreateDirectory(const SyncStatusCallback& callback) {
164  DCHECK(IsSyncFSDirectoryOperationEnabled());
165  api_util()->CreateDirectory(
166      origin_resource_id_,
167      DriveFileSyncService::PathToTitle(url_.path()),
168      base::Bind(&LocalSyncDelegate::DidCreateDirectory,
169                 weak_factory_.GetWeakPtr(), callback));
170}
171
172void LocalSyncDelegate::DidCreateDirectory(
173    const SyncStatusCallback& callback,
174    google_apis::GDataErrorCode error,
175    const std::string& resource_id) {
176  switch (error) {
177    case google_apis::HTTP_SUCCESS:
178    case google_apis::HTTP_CREATED: {
179      UpdateMetadata(
180          resource_id, std::string(), DriveMetadata::RESOURCE_TYPE_FOLDER,
181          base::Bind(&LocalSyncDelegate::DidApplyLocalChange,
182                     weak_factory_.GetWeakPtr(), callback, error));
183      sync_service_->NotifyObserversFileStatusChanged(
184          url_,
185          SYNC_FILE_STATUS_SYNCED,
186          SYNC_ACTION_ADDED,
187          SYNC_DIRECTION_LOCAL_TO_REMOTE);
188      return;
189    }
190
191    case google_apis::HTTP_CONFLICT:
192      // There were conflicts and a file was left.
193      // TODO(kinuko): Handle the latter case (http://crbug.com/237090).
194      // Fall-through
195
196    default:
197      callback.Run(GDataErrorCodeToSyncStatusCodeWrapper(error));
198  }
199}
200
201void LocalSyncDelegate::UploadExistingFile(const SyncStatusCallback& callback) {
202  DCHECK(has_drive_metadata_);
203  if (drive_metadata_.resource_id().empty()) {
204    UploadNewFile(callback);
205    return;
206  }
207
208  api_util()->UploadExistingFile(
209      drive_metadata_.resource_id(),
210      drive_metadata_.md5_checksum(),
211      local_path_,
212      base::Bind(&LocalSyncDelegate::DidUploadExistingFile,
213                 weak_factory_.GetWeakPtr(), callback));
214}
215
216void LocalSyncDelegate::DidUploadExistingFile(
217    const SyncStatusCallback& callback,
218    google_apis::GDataErrorCode error,
219    const std::string& resource_id,
220    const std::string& md5) {
221  DCHECK(has_drive_metadata_);
222  switch (error) {
223    case google_apis::HTTP_SUCCESS:
224      UpdateMetadata(
225          resource_id, md5, DriveMetadata::RESOURCE_TYPE_FILE,
226          base::Bind(&LocalSyncDelegate::DidApplyLocalChange,
227                     weak_factory_.GetWeakPtr(), callback, error));
228      sync_service_->NotifyObserversFileStatusChanged(
229          url_,
230          SYNC_FILE_STATUS_SYNCED,
231          SYNC_ACTION_UPDATED,
232          SYNC_DIRECTION_LOCAL_TO_REMOTE);
233      return;
234    case google_apis::HTTP_CONFLICT:
235      HandleConflict(callback);
236      return;
237    case google_apis::HTTP_NOT_MODIFIED:
238      DidApplyLocalChange(callback,
239                          google_apis::HTTP_SUCCESS, SYNC_STATUS_OK);
240      return;
241    case google_apis::HTTP_NOT_FOUND:
242      UploadNewFile(callback);
243      return;
244    default: {
245      const SyncStatusCode status =
246          GDataErrorCodeToSyncStatusCodeWrapper(error);
247      DCHECK_NE(SYNC_STATUS_OK, status);
248      callback.Run(status);
249      return;
250    }
251  }
252}
253
254void LocalSyncDelegate::Delete(const SyncStatusCallback& callback) {
255  if (!has_drive_metadata_) {
256    callback.Run(SYNC_STATUS_OK);
257    return;
258  }
259
260  if (drive_metadata_.resource_id().empty()) {
261    DidDelete(callback, google_apis::HTTP_NOT_FOUND);
262    return;
263  }
264
265  api_util()->DeleteFile(
266      drive_metadata_.resource_id(),
267      drive_metadata_.md5_checksum(),
268      base::Bind(&LocalSyncDelegate::DidDelete,
269                 weak_factory_.GetWeakPtr(), callback));
270}
271
272void LocalSyncDelegate::DidDelete(
273    const SyncStatusCallback& callback,
274    google_apis::GDataErrorCode error) {
275  DCHECK(has_drive_metadata_);
276
277  switch (error) {
278    case google_apis::HTTP_SUCCESS:
279    case google_apis::HTTP_NOT_FOUND:
280      DeleteMetadata(base::Bind(
281          &LocalSyncDelegate::DidApplyLocalChange,
282          weak_factory_.GetWeakPtr(), callback, google_apis::HTTP_SUCCESS));
283      sync_service_->NotifyObserversFileStatusChanged(
284          url_,
285          SYNC_FILE_STATUS_SYNCED,
286          SYNC_ACTION_DELETED,
287          SYNC_DIRECTION_LOCAL_TO_REMOTE);
288      return;
289    case google_apis::HTTP_PRECONDITION:
290    case google_apis::HTTP_CONFLICT:
291      // Delete |drive_metadata| on the conflict case.
292      // Conflicted remote change should be applied as a future remote change.
293      DeleteMetadata(base::Bind(
294          &LocalSyncDelegate::DidDeleteMetadataForDeletionConflict,
295          weak_factory_.GetWeakPtr(), callback));
296      sync_service_->NotifyObserversFileStatusChanged(
297          url_,
298          SYNC_FILE_STATUS_SYNCED,
299          SYNC_ACTION_DELETED,
300          SYNC_DIRECTION_LOCAL_TO_REMOTE);
301      return;
302    default: {
303      const SyncStatusCode status =
304          GDataErrorCodeToSyncStatusCodeWrapper(error);
305      DCHECK_NE(SYNC_STATUS_OK, status);
306      callback.Run(status);
307      return;
308    }
309  }
310}
311
312void LocalSyncDelegate::DidDeleteMetadataForDeletionConflict(
313    const SyncStatusCallback& callback,
314    SyncStatusCode status) {
315  callback.Run(SYNC_STATUS_OK);
316}
317
318void LocalSyncDelegate::ResolveToLocal(const SyncStatusCallback& callback) {
319  if (drive_metadata_.resource_id().empty()) {
320    DidDeleteFileToResolveToLocal(callback, google_apis::HTTP_NOT_FOUND);
321    return;
322  }
323
324  api_util()->DeleteFile(
325      drive_metadata_.resource_id(),
326      drive_metadata_.md5_checksum(),
327      base::Bind(
328          &LocalSyncDelegate::DidDeleteFileToResolveToLocal,
329          weak_factory_.GetWeakPtr(), callback));
330}
331
332void LocalSyncDelegate::DidDeleteFileToResolveToLocal(
333    const SyncStatusCallback& callback,
334    google_apis::GDataErrorCode error) {
335  if (error != google_apis::HTTP_SUCCESS &&
336      error != google_apis::HTTP_NOT_FOUND) {
337    callback.Run(GDataErrorCodeToSyncStatusCodeWrapper(error));
338    return;
339  }
340
341  DCHECK_NE(SYNC_FILE_TYPE_UNKNOWN, local_metadata_.file_type);
342  if (local_metadata_.file_type == SYNC_FILE_TYPE_FILE) {
343    UploadNewFile(callback);
344    return;
345  }
346
347  DCHECK(IsSyncFSDirectoryOperationEnabled());
348  DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_metadata_.file_type);
349  CreateDirectory(callback);
350}
351
352void LocalSyncDelegate::ResolveToRemote(
353    const SyncStatusCallback& callback,
354    SyncFileType remote_file_type) {
355  // Mark the file as to-be-fetched.
356  DCHECK(!drive_metadata_.resource_id().empty());
357
358  SetMetadataToBeFetched(
359      DriveFileSyncService::SyncFileTypeToDriveMetadataResourceType(
360          remote_file_type),
361      base::Bind(&LocalSyncDelegate::DidResolveToRemote,
362                 weak_factory_.GetWeakPtr(), callback));
363  // The synced notification will be dispatched when the remote file is
364  // downloaded.
365}
366
367void LocalSyncDelegate::DidResolveToRemote(
368    const SyncStatusCallback& callback,
369    SyncStatusCode status) {
370  DCHECK(has_drive_metadata_);
371  if (status != SYNC_STATUS_OK) {
372    callback.Run(status);
373    return;
374  }
375
376  SyncFileType file_type = SYNC_FILE_TYPE_FILE;
377  if (drive_metadata_.type() == DriveMetadata::RESOURCE_TYPE_FOLDER)
378    file_type = SYNC_FILE_TYPE_DIRECTORY;
379  sync_service_->AppendFetchChange(
380      url_.origin(), url_.path(), drive_metadata_.resource_id(), file_type);
381  callback.Run(status);
382}
383
384void LocalSyncDelegate::DidApplyLocalChange(
385    const SyncStatusCallback& callback,
386    const google_apis::GDataErrorCode error,
387    SyncStatusCode status) {
388  if ((operation_ == SYNC_OPERATION_DELETE ||
389       operation_ == SYNC_OPERATION_DELETE_METADATA) &&
390      (status == SYNC_FILE_ERROR_NOT_FOUND ||
391       status == SYNC_DATABASE_ERROR_NOT_FOUND)) {
392    status = SYNC_STATUS_OK;
393  }
394
395  if (status == SYNC_STATUS_OK) {
396    remote_change_handler()->RemoveChangeForURL(url_);
397    status = GDataErrorCodeToSyncStatusCodeWrapper(error);
398  }
399  callback.Run(status);
400}
401
402void LocalSyncDelegate::UpdateMetadata(
403    const std::string& resource_id,
404    const std::string& md5,
405    DriveMetadata::ResourceType type,
406    const SyncStatusCallback& callback) {
407  has_drive_metadata_ = true;
408  drive_metadata_.set_resource_id(resource_id);
409  drive_metadata_.set_md5_checksum(md5);
410  drive_metadata_.set_conflicted(false);
411  drive_metadata_.set_to_be_fetched(false);
412  drive_metadata_.set_type(type);
413  metadata_store()->UpdateEntry(url_, drive_metadata_, callback);
414}
415
416void LocalSyncDelegate::ResetMetadataForStartOver(
417    const SyncStatusCallback& callback) {
418  has_drive_metadata_ = true;
419  DCHECK(!drive_metadata_.resource_id().empty());
420  drive_metadata_.set_md5_checksum(std::string());
421  drive_metadata_.set_conflicted(false);
422  drive_metadata_.set_to_be_fetched(false);
423  metadata_store()->UpdateEntry(url_, drive_metadata_, callback);
424}
425
426void LocalSyncDelegate::SetMetadataToBeFetched(
427    DriveMetadata::ResourceType type,
428    const SyncStatusCallback& callback) {
429  has_drive_metadata_ = true;
430  drive_metadata_.set_md5_checksum(std::string());
431  drive_metadata_.set_conflicted(false);
432  drive_metadata_.set_to_be_fetched(true);
433  drive_metadata_.set_type(type);
434  metadata_store()->UpdateEntry(url_, drive_metadata_, callback);
435}
436
437void LocalSyncDelegate::DeleteMetadata(const SyncStatusCallback& callback) {
438  metadata_store()->DeleteEntry(url_, callback);
439}
440
441void LocalSyncDelegate::HandleCreationConflict(
442    const std::string& resource_id,
443    DriveMetadata::ResourceType type,
444    const SyncStatusCallback& callback) {
445  // File-file conflict is found.
446  // Populates a fake drive_metadata and set has_drive_metadata = true.
447  // In HandleConflictLocalSync:
448  // - If conflict_resolution is manual, we'll change conflicted to true
449  //   and save the metadata.
450  // - Otherwise we'll save the metadata with empty md5 and will start
451  //   over local sync as UploadExistingFile.
452  drive_metadata_.set_resource_id(resource_id);
453  drive_metadata_.set_md5_checksum(std::string());
454  drive_metadata_.set_conflicted(false);
455  drive_metadata_.set_to_be_fetched(false);
456  drive_metadata_.set_type(type);
457  has_drive_metadata_ = true;
458  HandleConflict(callback);
459}
460
461void LocalSyncDelegate::HandleConflict(const SyncStatusCallback& callback) {
462  DCHECK(!drive_metadata_.resource_id().empty());
463  api_util()->GetResourceEntry(
464      drive_metadata_.resource_id(),
465      base::Bind(
466          &LocalSyncDelegate::DidGetEntryForConflictResolution,
467          weak_factory_.GetWeakPtr(), callback));
468}
469
470void LocalSyncDelegate::DidGetEntryForConflictResolution(
471    const SyncStatusCallback& callback,
472    google_apis::GDataErrorCode error,
473    scoped_ptr<google_apis::ResourceEntry> entry) {
474  SyncFileType remote_file_type = SYNC_FILE_TYPE_UNKNOWN;
475  ConflictResolution resolution = CONFLICT_RESOLUTION_UNKNOWN;
476
477  if (error != google_apis::HTTP_SUCCESS ||
478      entry->updated_time().is_null()) {
479    resolution = CONFLICT_RESOLUTION_LOCAL_WIN;
480  } else {
481    SyncFileType local_file_type = local_metadata_.file_type;
482    base::Time local_modification_time = local_metadata_.last_modified;
483    base::Time remote_modification_time = entry->updated_time();
484    if (entry->is_file())
485      remote_file_type = SYNC_FILE_TYPE_FILE;
486    else if (entry->is_folder())
487      remote_file_type = SYNC_FILE_TYPE_DIRECTORY;
488    else
489      remote_file_type = SYNC_FILE_TYPE_UNKNOWN;
490
491    resolution = conflict_resolution_resolver()->Resolve(
492        local_file_type, local_modification_time,
493        remote_file_type, remote_modification_time);
494  }
495
496  switch (resolution) {
497    case CONFLICT_RESOLUTION_MARK_CONFLICT:
498      HandleManualResolutionCase(callback);
499      return;
500    case CONFLICT_RESOLUTION_LOCAL_WIN:
501      HandleLocalWinCase(callback);
502      return;
503    case CONFLICT_RESOLUTION_REMOTE_WIN:
504      HandleRemoteWinCase(callback, remote_file_type);
505      return;
506    case CONFLICT_RESOLUTION_UNKNOWN:
507      NOTREACHED();
508  }
509  NOTREACHED();
510  callback.Run(SYNC_STATUS_FAILED);
511}
512
513void LocalSyncDelegate::HandleManualResolutionCase(
514    const SyncStatusCallback& callback) {
515  if (drive_metadata_.conflicted()) {
516    callback.Run(SYNC_STATUS_HAS_CONFLICT);
517    return;
518  }
519
520  has_drive_metadata_ = true;
521  sync_service_->MarkConflict(
522      url_, &drive_metadata_,
523      base::Bind(&LocalSyncDelegate::DidMarkConflict,
524                 weak_factory_.GetWeakPtr(), callback));
525}
526
527void LocalSyncDelegate::DidMarkConflict(
528    const SyncStatusCallback& callback,
529    SyncStatusCode status) {
530  DidApplyLocalChange(callback, google_apis::HTTP_CONFLICT, status);
531}
532
533void LocalSyncDelegate::HandleLocalWinCase(
534    const SyncStatusCallback& callback) {
535  util::Log(logging::LOG_VERBOSE, FROM_HERE,
536            "Resolving conflict for local sync: %s: LOCAL WIN",
537            url_.DebugString().c_str());
538
539  DCHECK(!drive_metadata_.resource_id().empty());
540  if (!has_drive_metadata_) {
541    StartOver(callback, SYNC_STATUS_OK);
542    return;
543  }
544
545  ResetMetadataForStartOver(base::Bind(&LocalSyncDelegate::StartOver,
546                                       weak_factory_.GetWeakPtr(), callback));
547}
548
549void LocalSyncDelegate::HandleRemoteWinCase(
550    const SyncStatusCallback& callback,
551    SyncFileType remote_file_type) {
552  util::Log(logging::LOG_VERBOSE, FROM_HERE,
553            "Resolving conflict for local sync: %s: REMOTE WIN",
554            url_.DebugString().c_str());
555  ResolveToRemote(callback, remote_file_type);
556}
557
558void LocalSyncDelegate::StartOver(const SyncStatusCallback& callback,
559                                  SyncStatusCode status) {
560  if (status != SYNC_STATUS_OK) {
561    callback.Run(status);
562    return;
563  }
564
565  remote_change_handler()->RemoveChangeForURL(url_);
566
567  // Return the control back to the sync service once.
568  callback.Run(SYNC_STATUS_RETRY);
569}
570
571SyncStatusCode
572LocalSyncDelegate::GDataErrorCodeToSyncStatusCodeWrapper(
573    google_apis::GDataErrorCode error) {
574  return sync_service_->GDataErrorCodeToSyncStatusCodeWrapper(error);
575}
576
577DriveMetadataStore* LocalSyncDelegate::metadata_store() {
578  return sync_service_->metadata_store_.get();
579}
580
581APIUtilInterface* LocalSyncDelegate::api_util() {
582  return sync_service_->api_util_.get();
583}
584
585RemoteChangeHandler* LocalSyncDelegate::remote_change_handler() {
586  return &sync_service_->remote_change_handler_;
587}
588
589ConflictResolutionResolver* LocalSyncDelegate::conflict_resolution_resolver() {
590  return &sync_service_->conflict_resolution_resolver_;
591}
592
593}  // namespace drive_backend
594}  // namespace sync_file_system
595