| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/sync_file_system/drive_backend/local_to_remote_syncer.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "base/format_macros.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task_runner_util.h" |
| #include "chrome/browser/drive/drive_api_util.h" |
| #include "chrome/browser/drive/drive_service_interface.h" |
| #include "chrome/browser/drive/drive_uploader.h" |
| #include "chrome/browser/sync_file_system/drive_backend/callback_helper.h" |
| #include "chrome/browser/sync_file_system/drive_backend/drive_backend_constants.h" |
| #include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h" |
| #include "chrome/browser/sync_file_system/drive_backend/folder_creator.h" |
| #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h" |
| #include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h" |
| #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h" |
| #include "chrome/browser/sync_file_system/drive_backend/sync_task_manager.h" |
| #include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h" |
| #include "chrome/browser/sync_file_system/logger.h" |
| #include "google_apis/drive/drive_api_parser.h" |
| #include "net/base/mime_util.h" |
| #include "webkit/common/fileapi/file_system_util.h" |
| |
| namespace sync_file_system { |
| namespace drive_backend { |
| |
| namespace { |
| |
| scoped_ptr<FileTracker> FindTrackerByID(MetadataDatabase* metadata_database, |
| int64 tracker_id) { |
| scoped_ptr<FileTracker> tracker(new FileTracker); |
| if (metadata_database->FindTrackerByTrackerID(tracker_id, tracker.get())) |
| return tracker.Pass(); |
| return scoped_ptr<FileTracker>(); |
| } |
| |
| bool GetKnownChangeID(MetadataDatabase* metadata_database, |
| const std::string& file_id, |
| int64* change_id) { |
| FileMetadata remote_file_metadata; |
| if (!metadata_database->FindFileByFileID(file_id, &remote_file_metadata)) |
| return false; |
| *change_id = remote_file_metadata.details().change_id(); |
| return true; |
| } |
| |
| bool IsLocalFileMissing(const SyncFileMetadata& local_metadata, |
| const FileChange& local_change) { |
| return local_metadata.file_type == SYNC_FILE_TYPE_UNKNOWN || |
| local_change.IsDelete(); |
| } |
| |
| std::string GetMimeTypeFromTitle(const base::FilePath& title) { |
| base::FilePath::StringType extension = title.Extension(); |
| std::string mime_type; |
| if (extension.empty() || |
| !net::GetWellKnownMimeTypeFromExtension(extension.substr(1), &mime_type)) |
| return kMimeTypeOctetStream; |
| return mime_type; |
| } |
| |
| } // namespace |
| |
| LocalToRemoteSyncer::LocalToRemoteSyncer(SyncEngineContext* sync_context, |
| const SyncFileMetadata& local_metadata, |
| const FileChange& local_change, |
| const base::FilePath& local_path, |
| const fileapi::FileSystemURL& url) |
| : sync_context_(sync_context), |
| local_change_(local_change), |
| local_is_missing_(IsLocalFileMissing(local_metadata, local_change)), |
| local_path_(local_path), |
| url_(url), |
| sync_action_(SYNC_ACTION_NONE), |
| remote_file_change_id_(0), |
| retry_on_success_(false), |
| needs_remote_change_listing_(false), |
| weak_ptr_factory_(this) { |
| DCHECK(local_is_missing_ || |
| local_change.file_type() == local_metadata.file_type) |
| << local_change.DebugString() << " metadata:" << local_metadata.file_type; |
| } |
| |
| LocalToRemoteSyncer::~LocalToRemoteSyncer() { |
| } |
| |
| void LocalToRemoteSyncer::RunPreflight(scoped_ptr<SyncTaskToken> token) { |
| token->InitializeTaskLog("Local -> Remote"); |
| |
| if (!IsContextReady()) { |
| token->RecordLog("Context not ready."); |
| NOTREACHED(); |
| SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| token->RecordLog(base::StringPrintf( |
| "Start: %s on %s@%s %s", |
| local_change_.DebugString().c_str(), |
| url_.path().AsUTF8Unsafe().c_str(), |
| url_.origin().host().c_str(), |
| local_is_missing_ ? "(missing)" : "")); |
| |
| if (local_is_missing_ && !local_change_.IsDelete()) { |
| // Stray file, we can just return. |
| token->RecordLog("Missing file for non-delete change."); |
| SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_OK); |
| return; |
| } |
| |
| std::string app_id = url_.origin().host(); |
| base::FilePath path = url_.path(); |
| |
| scoped_ptr<FileTracker> active_ancestor_tracker(new FileTracker); |
| base::FilePath active_ancestor_path; |
| if (!metadata_database()->FindNearestActiveAncestor( |
| app_id, path, |
| active_ancestor_tracker.get(), &active_ancestor_path)) { |
| // The app is disabled or not registered. |
| token->RecordLog("App is disabled or not registered"); |
| SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_UNKNOWN_ORIGIN); |
| return; |
| } |
| DCHECK(active_ancestor_tracker->active()); |
| DCHECK(active_ancestor_tracker->has_synced_details()); |
| const FileDetails& active_ancestor_details = |
| active_ancestor_tracker->synced_details(); |
| |
| // TODO(tzik): Consider handling |
| // active_ancestor_tracker->synced_details().missing() case. |
| |
| DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE || |
| active_ancestor_details.file_kind() == FILE_KIND_FOLDER); |
| |
| base::FilePath missing_entries; |
| if (active_ancestor_path.empty()) { |
| missing_entries = path; |
| } else if (active_ancestor_path != path) { |
| if (!active_ancestor_path.AppendRelativePath(path, &missing_entries)) { |
| NOTREACHED(); |
| token->RecordLog(base::StringPrintf( |
| "Detected invalid ancestor: %s", |
| active_ancestor_path.value().c_str())); |
| SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| } |
| |
| std::vector<base::FilePath::StringType> missing_components; |
| fileapi::VirtualPath::GetComponents(missing_entries, &missing_components); |
| |
| if (!missing_components.empty()) { |
| if (local_is_missing_) { |
| token->RecordLog("Both local and remote are marked missing"); |
| // !IsDelete() but SYNC_FILE_TYPE_UNKNOWN could happen when a file is |
| // deleted by recursive deletion (which is not recorded by tracker) |
| // but there're remaining changes for the same file in the tracker. |
| |
| // Local file is deleted and remote file is missing, already deleted or |
| // not yet synced. There is nothing to do for the file. |
| SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_OK); |
| return; |
| } |
| } |
| |
| if (missing_components.size() > 1) { |
| // The original target doesn't have remote file and parent. |
| // Try creating the parent first. |
| if (active_ancestor_details.file_kind() == FILE_KIND_FOLDER) { |
| remote_parent_folder_tracker_ = active_ancestor_tracker.Pass(); |
| target_path_ = active_ancestor_path.Append(missing_components[0]); |
| token->RecordLog("Detected missing parent folder."); |
| |
| retry_on_success_ = true; |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::CreateRemoteFolder, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE); |
| remote_parent_folder_tracker_ = |
| FindTrackerByID(metadata_database(), |
| active_ancestor_tracker->parent_tracker_id()); |
| remote_file_tracker_ = active_ancestor_tracker.Pass(); |
| target_path_ = active_ancestor_path; |
| token->RecordLog("Detected non-folder file in its path."); |
| |
| retry_on_success_ = true; |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::DeleteRemoteFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| if (missing_components.empty()) { |
| // The original target has remote active file/folder. |
| remote_parent_folder_tracker_ = |
| FindTrackerByID(metadata_database(), |
| active_ancestor_tracker->parent_tracker_id()); |
| remote_file_tracker_ = active_ancestor_tracker.Pass(); |
| target_path_ = url_.path(); |
| DCHECK(target_path_ == active_ancestor_path); |
| |
| if (remote_file_tracker_->dirty()) { |
| token->RecordLog(base::StringPrintf( |
| "Detected conflicting dirty tracker:%" PRId64, |
| remote_file_tracker_->tracker_id())); |
| // Both local and remote file has pending modification. |
| HandleConflict(token.Pass()); |
| return; |
| } |
| |
| // Non-conflicting file/folder update case. |
| HandleExistingRemoteFile(token.Pass()); |
| return; |
| } |
| |
| DCHECK(local_change_.IsAddOrUpdate()); |
| DCHECK_EQ(1u, missing_components.size()); |
| // The original target has remote parent folder and doesn't have remote active |
| // file. |
| // Upload the file as a new file or create a folder. |
| remote_parent_folder_tracker_ = active_ancestor_tracker.Pass(); |
| target_path_ = url_.path(); |
| DCHECK(target_path_ == active_ancestor_path.Append(missing_components[0])); |
| if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) { |
| token->RecordLog("Detected a new file."); |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::UploadNewFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| token->RecordLog("Detected a new folder."); |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::CreateRemoteFolder, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| } |
| |
| void LocalToRemoteSyncer::MoveToBackground(const Continuation& continuation, |
| scoped_ptr<SyncTaskToken> token) { |
| scoped_ptr<BlockingFactor> blocker(new BlockingFactor); |
| blocker->app_id = url_.origin().host(); |
| blocker->paths.push_back(target_path_); |
| |
| if (remote_file_tracker_) { |
| if (!GetKnownChangeID(metadata_database(), |
| remote_file_tracker_->file_id(), |
| &remote_file_change_id_)) { |
| NOTREACHED(); |
| SyncCompleted(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| blocker->tracker_ids.push_back(remote_file_tracker_->tracker_id()); |
| blocker->file_ids.push_back(remote_file_tracker_->file_id()); |
| } |
| |
| // Run current task as a background task with |blocker|. |
| // After the invocation of ContinueAsBackgroundTask |
| SyncTaskManager::UpdateBlockingFactor( |
| token.Pass(), blocker.Pass(), |
| base::Bind(&LocalToRemoteSyncer::ContinueAsBackgroundTask, |
| weak_ptr_factory_.GetWeakPtr(), |
| continuation)); |
| } |
| |
| void LocalToRemoteSyncer::ContinueAsBackgroundTask( |
| const Continuation& continuation, |
| scoped_ptr<SyncTaskToken> token) { |
| // The SyncTask runs as a background task beyond this point. |
| // Note that any task can run between MoveToBackground() and |
| // ContinueAsBackgroundTask(), so we need to make sure other tasks didn't |
| // affect to the current LocalToRemoteSyncer task. |
| // |
| // - For RemoteToLocalSyncer, it doesn't actually run beyond |
| // PrepareForProcessRemoteChange() since it should be blocked in |
| // LocalFileSyncService. |
| // - For ListChangesTask, it may update FileMetatada together with |change_id| |
| // and may delete FileTracker. So, ensure |change_id| is not changed and |
| // check if FileTracker still exists. |
| // - For UninstallAppTask, it may also delete FileMetadata and FileTracker. |
| // And also, check if FileTracker still exists. |
| // - Others, SyncEngineInitializer and RegisterAppTask doesn't affect to |
| // LocalToRemoteSyncer. |
| if (remote_file_tracker_) { |
| int64 latest_change_id = 0; |
| if (!GetKnownChangeID(metadata_database(), |
| remote_file_tracker_->file_id(), |
| &latest_change_id) || |
| latest_change_id > remote_file_change_id_) { |
| SyncCompleted(token.Pass(), SYNC_STATUS_RETRY); |
| return; |
| } |
| |
| if (!metadata_database()->FindTrackerByTrackerID( |
| remote_file_tracker_->tracker_id(), NULL)) { |
| SyncCompleted(token.Pass(), SYNC_STATUS_RETRY); |
| return; |
| } |
| } |
| |
| continuation.Run(token.Pass()); |
| } |
| |
| void LocalToRemoteSyncer::SyncCompleted(scoped_ptr<SyncTaskToken> token, |
| SyncStatusCode status) { |
| if (status == SYNC_STATUS_OK && retry_on_success_) |
| status = SYNC_STATUS_RETRY; |
| |
| if (needs_remote_change_listing_) |
| status = SYNC_STATUS_FILE_BUSY; |
| |
| token->RecordLog(base::StringPrintf( |
| "Finished: action=%s, status=%s for %s@%s", |
| SyncActionToString(sync_action_), |
| SyncStatusCodeToString(status), |
| target_path_.AsUTF8Unsafe().c_str(), |
| url_.origin().host().c_str())); |
| |
| SyncTaskManager::NotifyTaskDone(token.Pass(), status); |
| } |
| |
| void LocalToRemoteSyncer::HandleConflict(scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_file_tracker_); |
| DCHECK(remote_file_tracker_->has_synced_details()); |
| DCHECK(remote_file_tracker_->active()); |
| DCHECK(remote_file_tracker_->dirty()); |
| |
| if (local_is_missing_) { |
| SyncCompleted(token.Pass(), SYNC_STATUS_OK); |
| return; |
| } |
| |
| if (local_change_.IsFile()) { |
| // Upload the conflicting file as a new file and let ConflictResolver |
| // resolve it. |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::UploadNewFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| DCHECK(local_change_.IsDirectory()); |
| // Check if we can reuse the remote folder. |
| FileMetadata remote_file_metadata; |
| if (!metadata_database()->FindFileByFileID( |
| remote_file_tracker_->file_id(), &remote_file_metadata)) { |
| NOTREACHED(); |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::CreateRemoteFolder, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| const FileDetails& remote_details = remote_file_metadata.details(); |
| base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); |
| if (!remote_details.missing() && |
| remote_details.file_kind() == FILE_KIND_FOLDER && |
| remote_details.title() == title.AsUTF8Unsafe() && |
| HasFileAsParent(remote_details, |
| remote_parent_folder_tracker_->file_id())) { |
| |
| MoveToBackground( |
| base::Bind(&LocalToRemoteSyncer::UpdateTrackerForReusedFolder, |
| weak_ptr_factory_.GetWeakPtr(), |
| remote_details), |
| token.Pass()); |
| return; |
| } |
| |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::CreateRemoteFolder, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| } |
| |
| void LocalToRemoteSyncer::UpdateTrackerForReusedFolder( |
| const FileDetails& details, |
| scoped_ptr<SyncTaskToken> token) { |
| metadata_database()->UpdateTracker( |
| remote_file_tracker_->tracker_id(), details, |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::HandleExistingRemoteFile( |
| scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_file_tracker_); |
| DCHECK(!remote_file_tracker_->dirty()); |
| DCHECK(remote_file_tracker_->active()); |
| DCHECK(remote_file_tracker_->has_synced_details()); |
| |
| if (local_is_missing_) { |
| // Local file deletion for existing remote file. |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::DeleteRemoteFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| DCHECK(local_change_.IsAddOrUpdate()); |
| DCHECK(local_change_.IsFile() || local_change_.IsDirectory()); |
| |
| const FileDetails& synced_details = remote_file_tracker_->synced_details(); |
| DCHECK(synced_details.file_kind() == FILE_KIND_FILE || |
| synced_details.file_kind() == FILE_KIND_FOLDER); |
| if (local_change_.IsFile()) { |
| if (synced_details.file_kind() == FILE_KIND_FILE) { |
| // Non-conflicting local file update to existing remote regular file. |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::UploadExistingFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind()); |
| // Non-conflicting local file update to existing remote *folder*. |
| // Assuming this case as local folder deletion + local file creation, delete |
| // the remote folder and upload the file. |
| retry_on_success_ = true; |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::DeleteRemoteFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| DCHECK(local_change_.IsDirectory()); |
| if (synced_details.file_kind() == FILE_KIND_FILE) { |
| // Non-conflicting local folder creation to existing remote *file*. |
| // Assuming this case as local file deletion + local folder creation, delete |
| // the remote file and create a remote folder. |
| retry_on_success_ = true; |
| MoveToBackground(base::Bind(&LocalToRemoteSyncer::DeleteRemoteFile, |
| weak_ptr_factory_.GetWeakPtr()), |
| token.Pass()); |
| return; |
| } |
| |
| // Non-conflicting local folder creation to existing remote folder. |
| DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind()); |
| SyncCompleted(token.Pass(), SYNC_STATUS_OK); |
| } |
| |
| void LocalToRemoteSyncer::DeleteRemoteFile(scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_file_tracker_); |
| DCHECK(remote_file_tracker_->has_synced_details()); |
| |
| sync_action_ = SYNC_ACTION_DELETED; |
| drive_service()->DeleteResource( |
| remote_file_tracker_->file_id(), |
| remote_file_tracker_->synced_details().etag(), |
| base::Bind(&LocalToRemoteSyncer::DidDeleteRemoteFile, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::DidDeleteRemoteFile( |
| scoped_ptr<SyncTaskToken> token, |
| google_apis::GDataErrorCode error) { |
| SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); |
| if (status != SYNC_STATUS_OK && |
| error != google_apis::HTTP_NOT_FOUND && |
| error != google_apis::HTTP_PRECONDITION && |
| error != google_apis::HTTP_CONFLICT) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| // Handle NOT_FOUND case as SUCCESS case. |
| // For PRECONDITION / CONFLICT case, the remote file is modified since the |
| // last sync completed. As our policy for deletion-modification conflict |
| // resolution, ignore the local deletion. |
| if (status == SYNC_STATUS_OK || |
| error == google_apis::HTTP_NOT_FOUND) { |
| metadata_database()->UpdateByDeletedRemoteFile( |
| remote_file_tracker_->file_id(), |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&token))); |
| return; |
| } |
| |
| SyncCompleted(token.Pass(), SYNC_STATUS_OK); |
| } |
| |
| void LocalToRemoteSyncer::UploadExistingFile(scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_file_tracker_); |
| DCHECK(remote_file_tracker_->has_synced_details()); |
| DCHECK(sync_context_->GetWorkerTaskRunner()->RunsTasksOnCurrentThread()); |
| |
| base::Callback<void(const std::string&)> did_calculate_callback = |
| base::Bind(&LocalToRemoteSyncer::DidGetMD5ForUpload, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&token)); |
| |
| sync_context_->GetFileTaskRunner()->PostTask( |
| FROM_HERE, |
| CreateComposedFunction( |
| base::Bind(&drive::util::GetMd5Digest, local_path_), |
| RelayCallbackToTaskRunner( |
| sync_context_->GetWorkerTaskRunner(), FROM_HERE, |
| did_calculate_callback))); |
| } |
| |
| void LocalToRemoteSyncer::DidGetMD5ForUpload( |
| scoped_ptr<SyncTaskToken> token, |
| const std::string& local_file_md5) { |
| if (local_file_md5 == remote_file_tracker_->synced_details().md5()) { |
| // Local file is not changed. |
| SyncCompleted(token.Pass(), SYNC_STATUS_OK); |
| return; |
| } |
| |
| sync_action_ = SYNC_ACTION_UPDATED; |
| |
| drive::DriveUploader::UploadExistingFileOptions options; |
| options.etag = remote_file_tracker_->synced_details().etag(); |
| drive_uploader()->UploadExistingFile( |
| remote_file_tracker_->file_id(), |
| local_path_, |
| "application/octet_stream", |
| options, |
| base::Bind(&LocalToRemoteSyncer::DidUploadExistingFile, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&token)), |
| google_apis::ProgressCallback()); |
| } |
| |
| void LocalToRemoteSyncer::DidUploadExistingFile( |
| scoped_ptr<SyncTaskToken> token, |
| google_apis::GDataErrorCode error, |
| const GURL&, |
| scoped_ptr<google_apis::FileResource> entry) { |
| if (error == google_apis::HTTP_PRECONDITION || |
| error == google_apis::HTTP_CONFLICT || |
| error == google_apis::HTTP_NOT_FOUND) { |
| // The remote file has unfetched remote change. Fetch latest metadata and |
| // update database with it. |
| // TODO(tzik): Consider adding local side low-priority dirtiness handling to |
| // handle this as ListChangesTask. |
| |
| needs_remote_change_listing_ = true; |
| UpdateRemoteMetadata( |
| remote_file_tracker_->file_id(), |
| token.Pass()); |
| return; |
| } |
| |
| SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); |
| if (status != SYNC_STATUS_OK) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| if (!entry) { |
| NOTREACHED(); |
| SyncCompleted(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| DCHECK(entry); |
| metadata_database()->UpdateByFileResource( |
| *entry, |
| base::Bind(&LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile( |
| scoped_ptr<SyncTaskToken> token, |
| SyncStatusCode status) { |
| if (status != SYNC_STATUS_OK) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| FileMetadata file; |
| if (!metadata_database()->FindFileByFileID( |
| remote_file_tracker_->file_id(), &file)) { |
| NOTREACHED(); |
| SyncCompleted(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| const FileDetails& details = file.details(); |
| base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); |
| if (!details.missing() && |
| details.file_kind() == FILE_KIND_FILE && |
| details.title() == title.AsUTF8Unsafe() && |
| HasFileAsParent(details, |
| remote_parent_folder_tracker_->file_id())) { |
| metadata_database()->UpdateTracker( |
| remote_file_tracker_->tracker_id(), |
| file.details(), |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| return; |
| } |
| |
| SyncCompleted(token.Pass(), SYNC_STATUS_RETRY); |
| } |
| |
| void LocalToRemoteSyncer::UpdateRemoteMetadata( |
| const std::string& file_id, |
| scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_file_tracker_); |
| |
| drive_service()->GetFileResource( |
| file_id, |
| base::Bind(&LocalToRemoteSyncer::DidGetRemoteMetadata, |
| weak_ptr_factory_.GetWeakPtr(), |
| file_id, base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::DidGetRemoteMetadata( |
| const std::string& file_id, |
| scoped_ptr<SyncTaskToken> token, |
| google_apis::GDataErrorCode error, |
| scoped_ptr<google_apis::FileResource> entry) { |
| DCHECK(sync_context_->GetWorkerTaskRunner()->RunsTasksOnCurrentThread()); |
| |
| if (error == google_apis::HTTP_NOT_FOUND) { |
| retry_on_success_ = true; |
| metadata_database()->UpdateByDeletedRemoteFile( |
| file_id, |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| return; |
| } |
| |
| SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); |
| if (status != SYNC_STATUS_OK) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| if (!entry) { |
| NOTREACHED(); |
| SyncCompleted(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| retry_on_success_ = true; |
| metadata_database()->UpdateByFileResource( |
| *entry, |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::UploadNewFile(scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_parent_folder_tracker_); |
| |
| sync_action_ = SYNC_ACTION_ADDED; |
| base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); |
| drive_uploader()->UploadNewFile( |
| remote_parent_folder_tracker_->file_id(), |
| local_path_, |
| title.AsUTF8Unsafe(), |
| GetMimeTypeFromTitle(title), |
| drive::DriveUploader::UploadNewFileOptions(), |
| base::Bind(&LocalToRemoteSyncer::DidUploadNewFile, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token)), |
| google_apis::ProgressCallback()); |
| } |
| |
| void LocalToRemoteSyncer::DidUploadNewFile( |
| scoped_ptr<SyncTaskToken> token, |
| google_apis::GDataErrorCode error, |
| const GURL& upload_location, |
| scoped_ptr<google_apis::FileResource> entry) { |
| if (error == google_apis::HTTP_NOT_FOUND) |
| needs_remote_change_listing_ = true; |
| |
| SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); |
| if (status != SYNC_STATUS_OK) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| if (!entry) { |
| NOTREACHED(); |
| SyncCompleted(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| metadata_database()->ReplaceActiveTrackerWithNewResource( |
| remote_parent_folder_tracker_->tracker_id(), *entry, |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::CreateRemoteFolder( |
| scoped_ptr<SyncTaskToken> token) { |
| DCHECK(remote_parent_folder_tracker_); |
| |
| base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); |
| sync_action_ = SYNC_ACTION_ADDED; |
| |
| DCHECK(!folder_creator_); |
| folder_creator_.reset(new FolderCreator( |
| drive_service(), metadata_database(), |
| remote_parent_folder_tracker_->file_id(), |
| title.AsUTF8Unsafe())); |
| folder_creator_->Run(base::Bind( |
| &LocalToRemoteSyncer::DidCreateRemoteFolder, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(&token))); |
| } |
| |
| void LocalToRemoteSyncer::DidCreateRemoteFolder( |
| scoped_ptr<SyncTaskToken> token, |
| const std::string& file_id, |
| SyncStatusCode status) { |
| if (status == SYNC_FILE_ERROR_NOT_FOUND) |
| needs_remote_change_listing_ = true; |
| |
| scoped_ptr<FolderCreator> deleter = folder_creator_.Pass(); |
| if (status != SYNC_STATUS_OK) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| MetadataDatabase::ActivationStatus activation_status = |
| metadata_database()->TryActivateTracker( |
| remote_parent_folder_tracker_->tracker_id(), |
| file_id, |
| base::Bind(&LocalToRemoteSyncer::SyncCompleted, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Passed(token.Pass()))); |
| switch (activation_status) { |
| case MetadataDatabase::ACTIVATION_PENDING: |
| // The task will be finalized by the callback passed to MetadataDatabase |
| // in this case. |
| return; |
| case MetadataDatabase::ACTIVATION_FAILED_ANOTHER_ACTIVE_TRACKER: |
| // The activation failed due to another tracker that has another parent. |
| // Detach the folder from the current parent to avoid using this folder as |
| // active folder. |
| drive_service()->RemoveResourceFromDirectory( |
| remote_parent_folder_tracker_->file_id(), file_id, |
| base::Bind(&LocalToRemoteSyncer::DidDetachResourceForCreationConflict, |
| weak_ptr_factory_.GetWeakPtr(), base::Passed(&token))); |
| return; |
| } |
| |
| NOTREACHED(); |
| SyncCompleted(token.Pass(), SYNC_STATUS_FAILED); |
| return; |
| } |
| |
| void LocalToRemoteSyncer::DidDetachResourceForCreationConflict( |
| scoped_ptr<SyncTaskToken> token, |
| google_apis::GDataErrorCode error) { |
| SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); |
| if (status != SYNC_STATUS_OK) { |
| SyncCompleted(token.Pass(), status); |
| return; |
| } |
| |
| SyncCompleted(token.Pass(), SYNC_STATUS_RETRY); |
| } |
| |
| bool LocalToRemoteSyncer::IsContextReady() { |
| return sync_context_->GetDriveService() && |
| sync_context_->GetDriveUploader() && |
| sync_context_->GetMetadataDatabase(); |
| } |
| |
| drive::DriveServiceInterface* LocalToRemoteSyncer::drive_service() { |
| set_used_network(true); |
| return sync_context_->GetDriveService(); |
| } |
| |
| drive::DriveUploaderInterface* LocalToRemoteSyncer::drive_uploader() { |
| set_used_network(true); |
| return sync_context_->GetDriveUploader(); |
| } |
| |
| MetadataDatabase* LocalToRemoteSyncer::metadata_database() { |
| return sync_context_->GetMetadataDatabase(); |
| } |
| |
| } // namespace drive_backend |
| } // namespace sync_file_system |