// Copyright (c) 2012 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/sync_file_system_service.h" #include #include "base/bind.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/stl_util.h" #include "chrome/browser/extensions/api/sync_file_system/extension_sync_event_observer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/sync_file_system/drive_file_sync_service.h" #include "chrome/browser/sync_file_system/local_file_sync_service.h" #include "chrome/browser/sync_file_system/logger.h" #include "chrome/browser/sync_file_system/sync_event_observer.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension.h" #include "components/browser_context_keyed_service/browser_context_dependency_manager.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "googleurl/src/gurl.h" #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/syncable/sync_direction.h" #include "webkit/browser/fileapi/syncable/sync_file_metadata.h" #include "webkit/browser/fileapi/syncable/sync_status_code.h" using content::BrowserThread; using fileapi::FileSystemURL; using fileapi::FileSystemURLSet; namespace sync_file_system { namespace { const int64 kRetryTimerIntervalInSeconds = 20 * 60; // 20 min. SyncServiceState RemoteStateToSyncServiceState( RemoteServiceState state) { switch (state) { case REMOTE_SERVICE_OK: return SYNC_SERVICE_RUNNING; case REMOTE_SERVICE_TEMPORARY_UNAVAILABLE: return SYNC_SERVICE_TEMPORARY_UNAVAILABLE; case REMOTE_SERVICE_AUTHENTICATION_REQUIRED: return SYNC_SERVICE_AUTHENTICATION_REQUIRED; case REMOTE_SERVICE_DISABLED: return SYNC_SERVICE_DISABLED; } NOTREACHED() << "Unknown remote service state: " << state; return SYNC_SERVICE_DISABLED; } void DidHandleOriginForExtensionUnloadedEvent( int type, extension_misc::UnloadedExtensionReason reason, const GURL& origin, SyncStatusCode code) { DCHECK(chrome::NOTIFICATION_EXTENSION_UNLOADED == type); DCHECK(extension_misc::UNLOAD_REASON_DISABLE == reason || extension_misc::UNLOAD_REASON_UNINSTALL == reason); if (code != SYNC_STATUS_OK && code != SYNC_STATUS_UNKNOWN_ORIGIN) { switch (reason) { case extension_misc::UNLOAD_REASON_DISABLE: util::Log(logging::LOG_WARNING, FROM_HERE, "Disabling origin for UNLOAD(DISABLE) failed: %s", origin.spec().c_str()); break; case extension_misc::UNLOAD_REASON_UNINSTALL: util::Log(logging::LOG_WARNING, FROM_HERE, "Uninstall origin for UNLOAD(UNINSTALL) failed: %s", origin.spec().c_str()); break; default: break; } } } void DidHandleOriginForExtensionEnabledEvent( int type, const GURL& origin, SyncStatusCode code) { DCHECK(chrome::NOTIFICATION_EXTENSION_ENABLED == type); if (code != SYNC_STATUS_OK) util::Log(logging::LOG_WARNING, FROM_HERE, "Enabling origin for ENABLED failed: %s", origin.spec().c_str()); } } // namespace void SyncFileSystemService::Shutdown() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); local_file_service_->Shutdown(); local_file_service_.reset(); remote_file_service_.reset(); ProfileSyncServiceBase* profile_sync_service = ProfileSyncServiceFactory::GetForProfile(profile_); if (profile_sync_service) profile_sync_service->RemoveObserver(this); profile_ = NULL; } SyncFileSystemService::~SyncFileSystemService() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(!profile_); } void SyncFileSystemService::InitializeForApp( fileapi::FileSystemContext* file_system_context, const std::string& service_name, const GURL& app_origin, const SyncStatusCallback& callback) { DCHECK(local_file_service_); DCHECK(remote_file_service_); DCHECK(app_origin == app_origin.GetOrigin()); DVLOG(1) << "InitializeForApp: " << app_origin.spec(); local_file_service_->MaybeInitializeFileSystemContext( app_origin, service_name, file_system_context, base::Bind(&SyncFileSystemService::DidInitializeFileSystem, AsWeakPtr(), app_origin, callback)); } SyncServiceState SyncFileSystemService::GetSyncServiceState() { return RemoteStateToSyncServiceState(remote_file_service_->GetCurrentState()); } void SyncFileSystemService::GetFileSyncStatus( const FileSystemURL& url, const SyncFileStatusCallback& callback) { DCHECK(local_file_service_); DCHECK(remote_file_service_); // It's possible to get an invalid FileEntry. if (!url.is_valid()) { base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(callback, SYNC_FILE_ERROR_INVALID_URL, SYNC_FILE_STATUS_UNKNOWN)); return; } if (remote_file_service_->IsConflicting(url)) { base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(callback, SYNC_STATUS_OK, SYNC_FILE_STATUS_CONFLICTING)); return; } local_file_service_->HasPendingLocalChanges( url, base::Bind(&SyncFileSystemService::DidGetLocalChangeStatus, AsWeakPtr(), callback)); } void SyncFileSystemService::AddSyncEventObserver(SyncEventObserver* observer) { observers_.AddObserver(observer); } void SyncFileSystemService::RemoveSyncEventObserver( SyncEventObserver* observer) { observers_.RemoveObserver(observer); } ConflictResolutionPolicy SyncFileSystemService::GetConflictResolutionPolicy() const { return remote_file_service_->GetConflictResolutionPolicy(); } SyncStatusCode SyncFileSystemService::SetConflictResolutionPolicy( ConflictResolutionPolicy policy) { return remote_file_service_->SetConflictResolutionPolicy(policy); } SyncFileSystemService::SyncFileSystemService(Profile* profile) : profile_(profile), pending_local_changes_(0), pending_remote_changes_(0), local_sync_running_(false), remote_sync_running_(false), is_waiting_remote_sync_enabled_(false), sync_enabled_(true) { } void SyncFileSystemService::Initialize( scoped_ptr local_file_service, scoped_ptr remote_file_service) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(local_file_service); DCHECK(remote_file_service); DCHECK(profile_); local_file_service_ = local_file_service.Pass(); remote_file_service_ = remote_file_service.Pass(); local_file_service_->AddChangeObserver(this); local_file_service_->SetLocalChangeProcessor( remote_file_service_->GetLocalChangeProcessor()); remote_file_service_->AddServiceObserver(this); remote_file_service_->AddFileStatusObserver(this); remote_file_service_->SetRemoteChangeProcessor(local_file_service_.get()); ProfileSyncServiceBase* profile_sync_service = ProfileSyncServiceFactory::GetForProfile(profile_); if (profile_sync_service) { UpdateSyncEnabledStatus(profile_sync_service); profile_sync_service->AddObserver(this); } registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, content::Source(profile_)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, content::Source(profile_)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_ENABLED, content::Source(profile_)); } void SyncFileSystemService::DidInitializeFileSystem( const GURL& app_origin, const SyncStatusCallback& callback, SyncStatusCode status) { DVLOG(1) << "DidInitializeFileSystem: " << app_origin.spec() << " " << status; if (status != SYNC_STATUS_OK) { callback.Run(status); return; } // Local side of initialization for the app is done. // Continue on initializing the remote side. remote_file_service_->RegisterOriginForTrackingChanges( app_origin, base::Bind(&SyncFileSystemService::DidRegisterOrigin, AsWeakPtr(), app_origin, callback)); } void SyncFileSystemService::DidRegisterOrigin( const GURL& app_origin, const SyncStatusCallback& callback, SyncStatusCode status) { DVLOG(1) << "DidRegisterOrigin: " << app_origin.spec() << " " << status; callback.Run(status); } void SyncFileSystemService::SetSyncEnabledForTesting(bool enabled) { sync_enabled_ = enabled; remote_file_service_->SetSyncEnabled(sync_enabled_); } void SyncFileSystemService::MaybeStartSync() { if (!profile_ || !sync_enabled_) return; if (pending_local_changes_ + pending_remote_changes_ == 0) return; DVLOG(2) << "MaybeStartSync() called (remote service state:" << remote_file_service_->GetCurrentState() << ")"; switch (remote_file_service_->GetCurrentState()) { case REMOTE_SERVICE_OK: break; case REMOTE_SERVICE_TEMPORARY_UNAVAILABLE: if (sync_retry_timer_.IsRunning()) return; sync_retry_timer_.Start( FROM_HERE, base::TimeDelta::FromSeconds(kRetryTimerIntervalInSeconds), this, &SyncFileSystemService::MaybeStartSync); break; case REMOTE_SERVICE_AUTHENTICATION_REQUIRED: case REMOTE_SERVICE_DISABLED: // No point to run sync. return; } StartRemoteSync(); StartLocalSync(); } void SyncFileSystemService::StartRemoteSync() { // See if we cannot / should not start a new remote sync. if (remote_sync_running_ || pending_remote_changes_ == 0) return; // If we have registered a URL for waiting until sync is enabled on a // file (and the registerred URL seems to be still valid) it won't be // worth trying to start another remote sync. if (is_waiting_remote_sync_enabled_) return; DCHECK(sync_enabled_); DVLOG(1) << "Calling ProcessRemoteChange"; remote_sync_running_ = true; remote_file_service_->ProcessRemoteChange( base::Bind(&SyncFileSystemService::DidProcessRemoteChange, AsWeakPtr())); } void SyncFileSystemService::StartLocalSync() { // See if we cannot / should not start a new local sync. if (local_sync_running_ || pending_local_changes_ == 0) return; DCHECK(sync_enabled_); DVLOG(1) << "Calling ProcessLocalChange"; local_sync_running_ = true; local_file_service_->ProcessLocalChange( base::Bind(&SyncFileSystemService::DidProcessLocalChange, AsWeakPtr())); } void SyncFileSystemService::DidProcessRemoteChange( SyncStatusCode status, const FileSystemURL& url) { DVLOG(1) << "DidProcessRemoteChange: " << " status=" << status << " (" << SyncStatusCodeToString(status) << ")" << " url=" << url.DebugString(); DCHECK(remote_sync_running_); remote_sync_running_ = false; if (status != SYNC_STATUS_NO_CHANGE_TO_SYNC && remote_file_service_->GetCurrentState() != REMOTE_SERVICE_DISABLED) { DCHECK(url.is_valid()); local_file_service_->ClearSyncFlagForURL(url); } if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC) { // We seem to have no changes to work on for now. // TODO(kinuko): Might be better setting a timer to call MaybeStartSync. return; } if (status == SYNC_STATUS_FILE_BUSY) { is_waiting_remote_sync_enabled_ = true; local_file_service_->RegisterURLForWaitingSync( url, base::Bind(&SyncFileSystemService::OnSyncEnabledForRemoteSync, AsWeakPtr())); return; } base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, AsWeakPtr())); } void SyncFileSystemService::DidProcessLocalChange( SyncStatusCode status, const FileSystemURL& url) { DVLOG(1) << "DidProcessLocalChange:" << " status=" << status << " (" << SyncStatusCodeToString(status) << ")" << " url=" << url.DebugString(); DCHECK(local_sync_running_); local_sync_running_ = false; if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC) { // We seem to have no changes to work on for now. return; } DCHECK(url.is_valid()); local_file_service_->ClearSyncFlagForURL(url); base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, AsWeakPtr())); } void SyncFileSystemService::DidGetLocalChangeStatus( const SyncFileStatusCallback& callback, SyncStatusCode status, bool has_pending_local_changes) { callback.Run( status, has_pending_local_changes ? SYNC_FILE_STATUS_HAS_PENDING_CHANGES : SYNC_FILE_STATUS_SYNCED); } void SyncFileSystemService::OnSyncEnabledForRemoteSync() { is_waiting_remote_sync_enabled_ = false; MaybeStartSync(); } void SyncFileSystemService::OnLocalChangeAvailable(int64 pending_changes) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK_GE(pending_changes, 0); DVLOG(1) << "OnLocalChangeAvailable: " << pending_changes; pending_local_changes_ = pending_changes; if (pending_changes == 0) return; base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, AsWeakPtr())); } void SyncFileSystemService::OnRemoteChangeQueueUpdated(int64 pending_changes) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK_GE(pending_changes, 0); DVLOG(1) << "OnRemoteChangeQueueUpdated: " << pending_changes; pending_remote_changes_ = pending_changes; if (pending_changes == 0) return; // The smallest change available might have changed from the previous one. // Reset the is_waiting_remote_sync_enabled_ flag so that we can retry. is_waiting_remote_sync_enabled_ = false; base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, AsWeakPtr())); } void SyncFileSystemService::OnRemoteServiceStateUpdated( RemoteServiceState state, const std::string& description) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DVLOG(1) << "OnRemoteServiceStateUpdated: " << state << " " << description; if (state == REMOTE_SERVICE_OK) { base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, AsWeakPtr())); } FOR_EACH_OBSERVER( SyncEventObserver, observers_, OnSyncStateUpdated(GURL(), RemoteStateToSyncServiceState(state), description)); } void SyncFileSystemService::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { // Event notification sequence. // // (User action) (Notification type) // Install: INSTALLED. // Update: INSTALLED. // Uninstall: UNLOADED(UNINSTALL). // Launch, Close: No notification. // Enable: EABLED. // Disable: UNLOADED(DISABLE). // Reload, Restart: UNLOADED(DISABLE) -> INSTALLED -> ENABLED. // switch (type) { case chrome::NOTIFICATION_EXTENSION_INSTALLED: HandleExtensionInstalled(details); break; case chrome::NOTIFICATION_EXTENSION_UNLOADED: HandleExtensionUnloaded(type, details); break; case chrome::NOTIFICATION_EXTENSION_ENABLED: HandleExtensionEnabled(type, details); break; default: NOTREACHED() << "Unknown notification."; break; } } void SyncFileSystemService::HandleExtensionInstalled( const content::NotificationDetails& details) { const extensions::Extension* extension = content::Details(details)-> extension; GURL app_origin = extensions::Extension::GetBaseURLFromExtensionId(extension->id()); DVLOG(1) << "Handle extension notification for INSTALLED: " << app_origin; // NOTE: When an app is uninstalled and re-installed in a sequence, // |local_file_service_| may still keeps |app_origin| as disabled origin. local_file_service_->SetOriginEnabled(app_origin, true); } void SyncFileSystemService::HandleExtensionUnloaded( int type, const content::NotificationDetails& details) { content::Details info(details); std::string extension_id = info->extension->id(); GURL app_origin = extensions::Extension::GetBaseURLFromExtensionId(extension_id); switch (info->reason) { case extension_misc::UNLOAD_REASON_DISABLE: DVLOG(1) << "Handle extension notification for UNLOAD(DISABLE): " << app_origin; remote_file_service_->DisableOriginForTrackingChanges( app_origin, base::Bind(&DidHandleOriginForExtensionUnloadedEvent, type, info->reason, app_origin)); local_file_service_->SetOriginEnabled(app_origin, false); break; case extension_misc::UNLOAD_REASON_UNINSTALL: DVLOG(1) << "Handle extension notification for UNLOAD(UNINSTALL): " << app_origin; remote_file_service_->UninstallOrigin( app_origin, base::Bind(&DidHandleOriginForExtensionUnloadedEvent, type, info->reason, app_origin)); local_file_service_->SetOriginEnabled(app_origin, false); break; default: // Nothing to do. break; } } void SyncFileSystemService::HandleExtensionEnabled( int type, const content::NotificationDetails& details) { std::string extension_id = content::Details(details)->id(); GURL app_origin = extensions::Extension::GetBaseURLFromExtensionId(extension_id); DVLOG(1) << "Handle extension notification for ENABLED: " << app_origin; remote_file_service_->EnableOriginForTrackingChanges( app_origin, base::Bind(&DidHandleOriginForExtensionEnabledEvent, type, app_origin)); local_file_service_->SetOriginEnabled(app_origin, true); } void SyncFileSystemService::OnStateChanged() { ProfileSyncServiceBase* profile_sync_service = ProfileSyncServiceFactory::GetForProfile(profile_); if (profile_sync_service) UpdateSyncEnabledStatus(profile_sync_service); } void SyncFileSystemService::OnFileStatusChanged( const FileSystemURL& url, SyncFileStatus sync_status, SyncAction action_taken, SyncDirection direction) { FOR_EACH_OBSERVER( SyncEventObserver, observers_, OnFileSynced(url, sync_status, action_taken, direction)); } void SyncFileSystemService::UpdateSyncEnabledStatus( ProfileSyncServiceBase* profile_sync_service) { if (!profile_sync_service->HasSyncSetupCompleted()) return; sync_enabled_ = profile_sync_service->GetPreferredDataTypes().Has( syncer::APPS); remote_file_service_->SetSyncEnabled(sync_enabled_); if (sync_enabled_) { base::MessageLoopProxy::current()->PostTask( FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, AsWeakPtr())); } } } // namespace sync_file_system