sync_file_system_service.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 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/sync_file_system_service.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/logging.h" 11#include "base/memory/ref_counted.h" 12#include "base/stl_util.h" 13#include "chrome/browser/extensions/api/sync_file_system/extension_sync_event_observer.h" 14#include "chrome/browser/profiles/profile.h" 15#include "chrome/browser/profiles/profile_dependency_manager.h" 16#include "chrome/browser/sync/profile_sync_service.h" 17#include "chrome/browser/sync/profile_sync_service_factory.h" 18#include "chrome/browser/sync_file_system/drive_file_sync_service.h" 19#include "chrome/browser/sync_file_system/local_file_sync_service.h" 20#include "chrome/browser/sync_file_system/sync_event_observer.h" 21#include "chrome/common/chrome_notification_types.h" 22#include "chrome/common/extensions/extension.h" 23#include "content/public/browser/browser_thread.h" 24#include "content/public/browser/notification_details.h" 25#include "content/public/browser/notification_service.h" 26#include "googleurl/src/gurl.h" 27#include "webkit/fileapi/file_system_context.h" 28#include "webkit/fileapi/syncable/sync_direction.h" 29#include "webkit/fileapi/syncable/sync_file_metadata.h" 30#include "webkit/fileapi/syncable/sync_status_code.h" 31 32using content::BrowserThread; 33using fileapi::FileSystemURL; 34using fileapi::FileSystemURLSet; 35 36namespace sync_file_system { 37 38namespace { 39 40const int64 kRetryTimerIntervalInSeconds = 20 * 60; // 20 min. 41 42SyncServiceState RemoteStateToSyncServiceState( 43 RemoteServiceState state) { 44 switch (state) { 45 case REMOTE_SERVICE_OK: 46 return SYNC_SERVICE_RUNNING; 47 case REMOTE_SERVICE_TEMPORARY_UNAVAILABLE: 48 return SYNC_SERVICE_TEMPORARY_UNAVAILABLE; 49 case REMOTE_SERVICE_AUTHENTICATION_REQUIRED: 50 return SYNC_SERVICE_AUTHENTICATION_REQUIRED; 51 case REMOTE_SERVICE_DISABLED: 52 return SYNC_SERVICE_DISABLED; 53 } 54 NOTREACHED() << "Unknown remote service state: " << state; 55 return SYNC_SERVICE_DISABLED; 56} 57 58void DidHandleOriginForExtensionUnloadedEvent( 59 int type, 60 extension_misc::UnloadedExtensionReason reason, 61 const GURL& origin, 62 SyncStatusCode code) { 63 DCHECK(chrome::NOTIFICATION_EXTENSION_UNLOADED == type); 64 DCHECK(extension_misc::UNLOAD_REASON_DISABLE == reason || 65 extension_misc::UNLOAD_REASON_UNINSTALL == reason); 66 if (code != SYNC_STATUS_OK) { 67 switch (reason) { 68 case extension_misc::UNLOAD_REASON_DISABLE: 69 LOG(WARNING) << "Disabling origin for UNLOAD(DISABLE) failed: " 70 << origin.spec(); 71 break; 72 case extension_misc::UNLOAD_REASON_UNINSTALL: 73 LOG(WARNING) << "Uninstall origin for UNLOAD(UNINSTALL) failed: " 74 << origin.spec(); 75 break; 76 default: 77 break; 78 } 79 } 80} 81 82void DidHandleOriginForExtensionEnabledEvent( 83 int type, 84 const GURL& origin, 85 SyncStatusCode code) { 86 DCHECK(chrome::NOTIFICATION_EXTENSION_ENABLED == type); 87 if (code != SYNC_STATUS_OK) 88 LOG(WARNING) << "Enabling origin for ENABLED failed: " << origin.spec(); 89} 90 91} // namespace 92 93void SyncFileSystemService::Shutdown() { 94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 95 96 local_file_service_->Shutdown(); 97 local_file_service_.reset(); 98 99 remote_file_service_.reset(); 100 101 ProfileSyncServiceBase* profile_sync_service = 102 ProfileSyncServiceFactory::GetForProfile(profile_); 103 if (profile_sync_service) 104 profile_sync_service->RemoveObserver(this); 105 106 profile_ = NULL; 107} 108 109SyncFileSystemService::~SyncFileSystemService() { 110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 111 DCHECK(!profile_); 112} 113 114void SyncFileSystemService::InitializeForApp( 115 fileapi::FileSystemContext* file_system_context, 116 const std::string& service_name, 117 const GURL& app_origin, 118 const SyncStatusCallback& callback) { 119 DCHECK(local_file_service_); 120 DCHECK(remote_file_service_); 121 DCHECK(app_origin == app_origin.GetOrigin()); 122 123 DVLOG(1) << "InitializeForApp: " << app_origin.spec(); 124 125 local_file_service_->MaybeInitializeFileSystemContext( 126 app_origin, service_name, file_system_context, 127 base::Bind(&SyncFileSystemService::DidInitializeFileSystem, 128 AsWeakPtr(), app_origin, callback)); 129} 130 131SyncServiceState SyncFileSystemService::GetSyncServiceState() { 132 return RemoteStateToSyncServiceState(remote_file_service_->GetCurrentState()); 133} 134 135void SyncFileSystemService::GetFileSyncStatus( 136 const FileSystemURL& url, const SyncFileStatusCallback& callback) { 137 DCHECK(local_file_service_); 138 DCHECK(remote_file_service_); 139 140 // It's possible to get an invalid FileEntry. 141 if (!url.is_valid()) { 142 base::MessageLoopProxy::current()->PostTask( 143 FROM_HERE, 144 base::Bind(callback, 145 SYNC_FILE_ERROR_INVALID_URL, 146 SYNC_FILE_STATUS_UNKNOWN)); 147 return; 148 } 149 150 if (remote_file_service_->IsConflicting(url)) { 151 base::MessageLoopProxy::current()->PostTask( 152 FROM_HERE, 153 base::Bind(callback, 154 SYNC_STATUS_OK, 155 SYNC_FILE_STATUS_CONFLICTING)); 156 return; 157 } 158 159 local_file_service_->HasPendingLocalChanges( 160 url, 161 base::Bind(&SyncFileSystemService::DidGetLocalChangeStatus, 162 AsWeakPtr(), callback)); 163} 164 165void SyncFileSystemService::AddSyncEventObserver(SyncEventObserver* observer) { 166 observers_.AddObserver(observer); 167} 168 169void SyncFileSystemService::RemoveSyncEventObserver( 170 SyncEventObserver* observer) { 171 observers_.RemoveObserver(observer); 172} 173 174ConflictResolutionPolicy 175SyncFileSystemService::GetConflictResolutionPolicy() const { 176 return remote_file_service_->GetConflictResolutionPolicy(); 177} 178 179SyncStatusCode SyncFileSystemService::SetConflictResolutionPolicy( 180 ConflictResolutionPolicy policy) { 181 return remote_file_service_->SetConflictResolutionPolicy(policy); 182} 183 184SyncFileSystemService::SyncFileSystemService(Profile* profile) 185 : profile_(profile), 186 pending_local_changes_(0), 187 pending_remote_changes_(0), 188 local_sync_running_(false), 189 remote_sync_running_(false), 190 is_waiting_remote_sync_enabled_(false), 191 sync_enabled_(true) { 192} 193 194void SyncFileSystemService::Initialize( 195 scoped_ptr<LocalFileSyncService> local_file_service, 196 scoped_ptr<RemoteFileSyncService> remote_file_service) { 197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 198 DCHECK(local_file_service); 199 DCHECK(remote_file_service); 200 DCHECK(profile_); 201 202 local_file_service_ = local_file_service.Pass(); 203 remote_file_service_ = remote_file_service.Pass(); 204 205 local_file_service_->AddChangeObserver(this); 206 local_file_service_->SetLocalChangeProcessor( 207 remote_file_service_->GetLocalChangeProcessor()); 208 209 remote_file_service_->AddServiceObserver(this); 210 remote_file_service_->AddFileStatusObserver(this); 211 remote_file_service_->SetRemoteChangeProcessor(local_file_service_.get()); 212 213 ProfileSyncServiceBase* profile_sync_service = 214 ProfileSyncServiceFactory::GetForProfile(profile_); 215 if (profile_sync_service) { 216 UpdateSyncEnabledStatus(profile_sync_service); 217 profile_sync_service->AddObserver(this); 218 } 219 220 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, 221 content::Source<Profile>(profile_)); 222 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, 223 content::Source<Profile>(profile_)); 224 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_ENABLED, 225 content::Source<Profile>(profile_)); 226} 227 228void SyncFileSystemService::DidInitializeFileSystem( 229 const GURL& app_origin, 230 const SyncStatusCallback& callback, 231 SyncStatusCode status) { 232 DVLOG(1) << "DidInitializeFileSystem: " 233 << app_origin.spec() << " " << status; 234 235 if (status != SYNC_STATUS_OK) { 236 callback.Run(status); 237 return; 238 } 239 240 // Local side of initialization for the app is done. 241 // Continue on initializing the remote side. 242 remote_file_service_->RegisterOriginForTrackingChanges( 243 app_origin, 244 base::Bind(&SyncFileSystemService::DidRegisterOrigin, 245 AsWeakPtr(), app_origin, callback)); 246} 247 248void SyncFileSystemService::DidRegisterOrigin( 249 const GURL& app_origin, 250 const SyncStatusCallback& callback, 251 SyncStatusCode status) { 252 DVLOG(1) << "DidRegisterOrigin: " << app_origin.spec() << " " << status; 253 254 callback.Run(status); 255} 256 257void SyncFileSystemService::SetSyncEnabledForTesting(bool enabled) { 258 sync_enabled_ = enabled; 259 remote_file_service_->SetSyncEnabled(sync_enabled_); 260} 261 262void SyncFileSystemService::MaybeStartSync() { 263 if (!profile_ || !sync_enabled_) 264 return; 265 266 if (pending_local_changes_ + pending_remote_changes_ == 0) 267 return; 268 269 DVLOG(2) << "MaybeStartSync() called (remote service state:" 270 << remote_file_service_->GetCurrentState() << ")"; 271 switch (remote_file_service_->GetCurrentState()) { 272 case REMOTE_SERVICE_OK: 273 break; 274 275 case REMOTE_SERVICE_TEMPORARY_UNAVAILABLE: 276 if (sync_retry_timer_.IsRunning()) 277 return; 278 sync_retry_timer_.Start( 279 FROM_HERE, 280 base::TimeDelta::FromSeconds(kRetryTimerIntervalInSeconds), 281 this, &SyncFileSystemService::MaybeStartSync); 282 break; 283 284 case REMOTE_SERVICE_AUTHENTICATION_REQUIRED: 285 case REMOTE_SERVICE_DISABLED: 286 // No point to run sync. 287 return; 288 } 289 290 StartRemoteSync(); 291 StartLocalSync(); 292} 293 294void SyncFileSystemService::StartRemoteSync() { 295 // See if we cannot / should not start a new remote sync. 296 if (remote_sync_running_ || pending_remote_changes_ == 0) 297 return; 298 // If we have registered a URL for waiting until sync is enabled on a 299 // file (and the registerred URL seems to be still valid) it won't be 300 // worth trying to start another remote sync. 301 if (is_waiting_remote_sync_enabled_) 302 return; 303 DCHECK(sync_enabled_); 304 305 DVLOG(1) << "Calling ProcessRemoteChange"; 306 remote_sync_running_ = true; 307 remote_file_service_->ProcessRemoteChange( 308 base::Bind(&SyncFileSystemService::DidProcessRemoteChange, 309 AsWeakPtr())); 310} 311 312void SyncFileSystemService::StartLocalSync() { 313 // See if we cannot / should not start a new local sync. 314 if (local_sync_running_ || pending_local_changes_ == 0) 315 return; 316 DCHECK(sync_enabled_); 317 318 DVLOG(1) << "Calling ProcessLocalChange"; 319 local_sync_running_ = true; 320 local_file_service_->ProcessLocalChange( 321 base::Bind(&SyncFileSystemService::DidProcessLocalChange, 322 AsWeakPtr())); 323} 324 325void SyncFileSystemService::DidProcessRemoteChange( 326 SyncStatusCode status, 327 const FileSystemURL& url) { 328 DVLOG(1) << "DidProcessRemoteChange: " 329 << " status=" << status 330 << " (" << SyncStatusCodeToString(status) << ")" 331 << " url=" << url.DebugString(); 332 DCHECK(remote_sync_running_); 333 remote_sync_running_ = false; 334 335 if (status != SYNC_STATUS_NO_CHANGE_TO_SYNC && 336 remote_file_service_->GetCurrentState() != REMOTE_SERVICE_DISABLED) { 337 DCHECK(url.is_valid()); 338 local_file_service_->ClearSyncFlagForURL(url); 339 } 340 341 if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC) { 342 // We seem to have no changes to work on for now. 343 // TODO(kinuko): Might be better setting a timer to call MaybeStartSync. 344 return; 345 } 346 if (status == SYNC_STATUS_FILE_BUSY) { 347 is_waiting_remote_sync_enabled_ = true; 348 local_file_service_->RegisterURLForWaitingSync( 349 url, base::Bind(&SyncFileSystemService::OnSyncEnabledForRemoteSync, 350 AsWeakPtr())); 351 return; 352 } 353 354 base::MessageLoopProxy::current()->PostTask( 355 FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, 356 AsWeakPtr())); 357} 358 359void SyncFileSystemService::DidProcessLocalChange( 360 SyncStatusCode status, const FileSystemURL& url) { 361 DVLOG(1) << "DidProcessLocalChange:" 362 << " status=" << status 363 << " (" << SyncStatusCodeToString(status) << ")" 364 << " url=" << url.DebugString(); 365 DCHECK(local_sync_running_); 366 local_sync_running_ = false; 367 368 if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC) { 369 // We seem to have no changes to work on for now. 370 return; 371 } 372 373 DCHECK(url.is_valid()); 374 local_file_service_->ClearSyncFlagForURL(url); 375 376 base::MessageLoopProxy::current()->PostTask( 377 FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, 378 AsWeakPtr())); 379} 380 381void SyncFileSystemService::DidGetLocalChangeStatus( 382 const SyncFileStatusCallback& callback, 383 SyncStatusCode status, 384 bool has_pending_local_changes) { 385 callback.Run( 386 status, 387 has_pending_local_changes ? 388 SYNC_FILE_STATUS_HAS_PENDING_CHANGES : SYNC_FILE_STATUS_SYNCED); 389} 390 391void SyncFileSystemService::OnSyncEnabledForRemoteSync() { 392 is_waiting_remote_sync_enabled_ = false; 393 MaybeStartSync(); 394} 395 396void SyncFileSystemService::OnLocalChangeAvailable(int64 pending_changes) { 397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 398 DCHECK_GE(pending_changes, 0); 399 DVLOG(1) << "OnLocalChangeAvailable: " << pending_changes; 400 pending_local_changes_ = pending_changes; 401 if (pending_changes == 0) 402 return; 403 404 base::MessageLoopProxy::current()->PostTask( 405 FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, 406 AsWeakPtr())); 407} 408 409void SyncFileSystemService::OnRemoteChangeQueueUpdated(int64 pending_changes) { 410 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 411 DCHECK_GE(pending_changes, 0); 412 DVLOG(1) << "OnRemoteChangeQueueUpdated: " << pending_changes; 413 pending_remote_changes_ = pending_changes; 414 if (pending_changes == 0) 415 return; 416 417 // The smallest change available might have changed from the previous one. 418 // Reset the is_waiting_remote_sync_enabled_ flag so that we can retry. 419 is_waiting_remote_sync_enabled_ = false; 420 421 base::MessageLoopProxy::current()->PostTask( 422 FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, 423 AsWeakPtr())); 424} 425 426void SyncFileSystemService::OnRemoteServiceStateUpdated( 427 RemoteServiceState state, 428 const std::string& description) { 429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 430 DVLOG(1) << "OnRemoteServiceStateUpdated: " << state 431 << " " << description; 432 433 if (state == REMOTE_SERVICE_OK) { 434 base::MessageLoopProxy::current()->PostTask( 435 FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, 436 AsWeakPtr())); 437 } 438 439 FOR_EACH_OBSERVER( 440 SyncEventObserver, observers_, 441 OnSyncStateUpdated(GURL(), 442 RemoteStateToSyncServiceState(state), 443 description)); 444} 445 446void SyncFileSystemService::Observe( 447 int type, 448 const content::NotificationSource& source, 449 const content::NotificationDetails& details) { 450 // Event notification sequence. 451 // 452 // (User action) (Notification type) 453 // Install: INSTALLED. 454 // Update: INSTALLED. 455 // Uninstall: UNLOADED(UNINSTALL). 456 // Launch, Close: No notification. 457 // Enable: EABLED. 458 // Disable: UNLOADED(DISABLE). 459 // Reload, Restart: UNLOADED(DISABLE) -> INSTALLED -> ENABLED. 460 // 461 switch (type) { 462 case chrome::NOTIFICATION_EXTENSION_INSTALLED: 463 HandleExtensionInstalled(details); 464 break; 465 case chrome::NOTIFICATION_EXTENSION_UNLOADED: 466 HandleExtensionUnloaded(type, details); 467 break; 468 case chrome::NOTIFICATION_EXTENSION_ENABLED: 469 HandleExtensionEnabled(type, details); 470 break; 471 default: 472 NOTREACHED() << "Unknown notification."; 473 break; 474 } 475} 476 477void SyncFileSystemService::HandleExtensionInstalled( 478 const content::NotificationDetails& details) { 479 const extensions::Extension* extension = 480 content::Details<const extensions::InstalledExtensionInfo>(details)-> 481 extension; 482 GURL app_origin = 483 extensions::Extension::GetBaseURLFromExtensionId(extension->id()); 484 DVLOG(1) << "Handle extension notification for INSTALLED: " << app_origin; 485 // NOTE: When an app is uninstalled and re-installed in a sequence, 486 // |local_file_service_| may still keeps |app_origin| as disabled origin. 487 local_file_service_->SetOriginEnabled(app_origin, true); 488} 489 490void SyncFileSystemService::HandleExtensionUnloaded( 491 int type, 492 const content::NotificationDetails& details) { 493 content::Details<const extensions::UnloadedExtensionInfo> info(details); 494 std::string extension_id = info->extension->id(); 495 GURL app_origin = 496 extensions::Extension::GetBaseURLFromExtensionId(extension_id); 497 498 switch (info->reason) { 499 case extension_misc::UNLOAD_REASON_DISABLE: 500 DVLOG(1) << "Handle extension notification for UNLOAD(DISABLE): " 501 << app_origin; 502 remote_file_service_->DisableOriginForTrackingChanges( 503 app_origin, 504 base::Bind(&DidHandleOriginForExtensionUnloadedEvent, 505 type, info->reason, app_origin)); 506 local_file_service_->SetOriginEnabled(app_origin, false); 507 break; 508 case extension_misc::UNLOAD_REASON_UNINSTALL: 509 DVLOG(1) << "Handle extension notification for UNLOAD(UNINSTALL): " 510 << app_origin; 511 remote_file_service_->UninstallOrigin( 512 app_origin, 513 base::Bind(&DidHandleOriginForExtensionUnloadedEvent, 514 type, info->reason, app_origin)); 515 local_file_service_->SetOriginEnabled(app_origin, false); 516 break; 517 default: 518 // Nothing to do. 519 break; 520 } 521} 522 523void SyncFileSystemService::HandleExtensionEnabled( 524 int type, 525 const content::NotificationDetails& details) { 526 std::string extension_id = 527 content::Details<const extensions::Extension>(details)->id(); 528 GURL app_origin = 529 extensions::Extension::GetBaseURLFromExtensionId(extension_id); 530 DVLOG(1) << "Handle extension notification for ENABLED: " << app_origin; 531 remote_file_service_->EnableOriginForTrackingChanges( 532 app_origin, 533 base::Bind(&DidHandleOriginForExtensionEnabledEvent, type, app_origin)); 534 local_file_service_->SetOriginEnabled(app_origin, true); 535} 536 537void SyncFileSystemService::OnStateChanged() { 538 ProfileSyncServiceBase* profile_sync_service = 539 ProfileSyncServiceFactory::GetForProfile(profile_); 540 if (profile_sync_service) 541 UpdateSyncEnabledStatus(profile_sync_service); 542} 543 544void SyncFileSystemService::OnFileStatusChanged( 545 const FileSystemURL& url, 546 SyncFileStatus sync_status, 547 SyncAction action_taken, 548 SyncDirection direction) { 549 FOR_EACH_OBSERVER( 550 SyncEventObserver, observers_, 551 OnFileSynced(url, sync_status, action_taken, direction)); 552} 553 554void SyncFileSystemService::UpdateSyncEnabledStatus( 555 ProfileSyncServiceBase* profile_sync_service) { 556 if (!profile_sync_service->HasSyncSetupCompleted()) 557 return; 558 sync_enabled_ = profile_sync_service->GetPreferredDataTypes().Has( 559 syncer::APPS); 560 remote_file_service_->SetSyncEnabled(sync_enabled_); 561 if (sync_enabled_) { 562 base::MessageLoopProxy::current()->PostTask( 563 FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync, 564 AsWeakPtr())); 565 } 566} 567 568} // namespace sync_file_system 569