local_file_sync_service.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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/local/local_file_sync_service.h" 6 7#include "base/single_thread_task_runner.h" 8#include "base/stl_util.h" 9#include "base/thread_task_runner_handle.h" 10#include "chrome/browser/extensions/extension_util.h" 11#include "chrome/browser/profiles/profile.h" 12#include "chrome/browser/sync_file_system/file_change.h" 13#include "chrome/browser/sync_file_system/local/local_file_change_tracker.h" 14#include "chrome/browser/sync_file_system/local/local_file_sync_context.h" 15#include "chrome/browser/sync_file_system/local/sync_file_system_backend.h" 16#include "chrome/browser/sync_file_system/local_change_processor.h" 17#include "chrome/browser/sync_file_system/logger.h" 18#include "chrome/browser/sync_file_system/sync_file_metadata.h" 19#include "content/public/browser/browser_context.h" 20#include "content/public/browser/browser_thread.h" 21#include "content/public/browser/site_instance.h" 22#include "content/public/browser/storage_partition.h" 23#include "extensions/browser/extension_registry.h" 24#include "extensions/common/extension_set.h" 25#include "url/gurl.h" 26#include "webkit/browser/fileapi/file_system_context.h" 27#include "webkit/browser/fileapi/file_system_url.h" 28#include "webkit/common/blob/scoped_file.h" 29 30using content::BrowserThread; 31using storage::FileSystemURL; 32 33namespace sync_file_system { 34 35namespace { 36 37void PrepareForProcessRemoteChangeCallbackAdapter( 38 const RemoteChangeProcessor::PrepareChangeCallback& callback, 39 SyncStatusCode status, 40 const LocalFileSyncInfo& sync_file_info, 41 storage::ScopedFile snapshot) { 42 callback.Run(status, sync_file_info.metadata, sync_file_info.changes); 43} 44 45void InvokeCallbackOnNthInvocation(int* count, const base::Closure& callback) { 46 --*count; 47 if (*count <= 0) 48 callback.Run(); 49} 50 51} // namespace 52 53LocalFileSyncService::OriginChangeMap::OriginChangeMap() 54 : next_(change_count_map_.end()) {} 55LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {} 56 57bool LocalFileSyncService::OriginChangeMap::NextOriginToProcess(GURL* origin) { 58 DCHECK(origin); 59 if (change_count_map_.empty()) 60 return false; 61 Map::iterator begin = next_; 62 do { 63 if (next_ == change_count_map_.end()) 64 next_ = change_count_map_.begin(); 65 DCHECK_NE(0, next_->second); 66 *origin = next_++->first; 67 if (!ContainsKey(disabled_origins_, *origin)) 68 return true; 69 } while (next_ != begin); 70 return false; 71} 72 73int64 LocalFileSyncService::OriginChangeMap::GetTotalChangeCount() const { 74 int64 num_changes = 0; 75 for (Map::const_iterator iter = change_count_map_.begin(); 76 iter != change_count_map_.end(); ++iter) { 77 if (ContainsKey(disabled_origins_, iter->first)) 78 continue; 79 num_changes += iter->second; 80 } 81 return num_changes; 82} 83 84void LocalFileSyncService::OriginChangeMap::SetOriginChangeCount( 85 const GURL& origin, int64 changes) { 86 if (changes != 0) { 87 change_count_map_[origin] = changes; 88 return; 89 } 90 Map::iterator found = change_count_map_.find(origin); 91 if (found != change_count_map_.end()) { 92 if (next_ == found) 93 ++next_; 94 change_count_map_.erase(found); 95 } 96} 97 98void LocalFileSyncService::OriginChangeMap::SetOriginEnabled( 99 const GURL& origin, bool enabled) { 100 if (enabled) 101 disabled_origins_.erase(origin); 102 else 103 disabled_origins_.insert(origin); 104} 105 106// LocalFileSyncService ------------------------------------------------------- 107 108scoped_ptr<LocalFileSyncService> LocalFileSyncService::Create( 109 Profile* profile) { 110 return make_scoped_ptr(new LocalFileSyncService(profile, NULL)); 111} 112 113scoped_ptr<LocalFileSyncService> LocalFileSyncService::CreateForTesting( 114 Profile* profile, 115 leveldb::Env* env) { 116 scoped_ptr<LocalFileSyncService> sync_service( 117 new LocalFileSyncService(profile, env)); 118 sync_service->sync_context_->set_mock_notify_changes_duration_in_sec(0); 119 return sync_service.Pass(); 120} 121 122LocalFileSyncService::~LocalFileSyncService() { 123 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 124} 125 126void LocalFileSyncService::Shutdown() { 127 sync_context_->RemoveOriginChangeObserver(this); 128 sync_context_->ShutdownOnUIThread(); 129 profile_ = NULL; 130} 131 132void LocalFileSyncService::MaybeInitializeFileSystemContext( 133 const GURL& app_origin, 134 storage::FileSystemContext* file_system_context, 135 const SyncStatusCallback& callback) { 136 sync_context_->MaybeInitializeFileSystemContext( 137 app_origin, file_system_context, 138 base::Bind(&LocalFileSyncService::DidInitializeFileSystemContext, 139 AsWeakPtr(), app_origin, 140 make_scoped_refptr(file_system_context), callback)); 141} 142 143void LocalFileSyncService::AddChangeObserver(Observer* observer) { 144 change_observers_.AddObserver(observer); 145} 146 147void LocalFileSyncService::RegisterURLForWaitingSync( 148 const FileSystemURL& url, 149 const base::Closure& on_syncable_callback) { 150 sync_context_->RegisterURLForWaitingSync(url, on_syncable_callback); 151} 152 153void LocalFileSyncService::ProcessLocalChange( 154 const SyncFileCallback& callback) { 155 // Pick an origin to process next. 156 GURL origin; 157 if (!origin_change_map_.NextOriginToProcess(&origin)) { 158 callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC, FileSystemURL()); 159 return; 160 } 161 DCHECK(local_sync_callback_.is_null()); 162 DCHECK(!origin.is_empty()); 163 DCHECK(ContainsKey(origin_to_contexts_, origin)); 164 165 DVLOG(1) << "Starting ProcessLocalChange"; 166 167 local_sync_callback_ = callback; 168 169 sync_context_->GetFileForLocalSync( 170 origin_to_contexts_[origin], 171 base::Bind(&LocalFileSyncService::DidGetFileForLocalSync, 172 AsWeakPtr())); 173} 174 175void LocalFileSyncService::SetLocalChangeProcessor( 176 LocalChangeProcessor* local_change_processor) { 177 local_change_processor_ = local_change_processor; 178} 179 180void LocalFileSyncService::SetLocalChangeProcessorCallback( 181 const GetLocalChangeProcessorCallback& get_local_change_processor) { 182 get_local_change_processor_ = get_local_change_processor; 183} 184 185void LocalFileSyncService::HasPendingLocalChanges( 186 const FileSystemURL& url, 187 const HasPendingLocalChangeCallback& callback) { 188 if (!ContainsKey(origin_to_contexts_, url.origin())) { 189 base::ThreadTaskRunnerHandle::Get()->PostTask( 190 FROM_HERE, 191 base::Bind(callback, SYNC_FILE_ERROR_INVALID_URL, false)); 192 return; 193 } 194 sync_context_->HasPendingLocalChanges( 195 origin_to_contexts_[url.origin()], url, callback); 196} 197 198void LocalFileSyncService::PromoteDemotedChanges( 199 const base::Closure& callback) { 200 if (origin_to_contexts_.empty()) { 201 callback.Run(); 202 return; 203 } 204 205 base::Closure completion_callback = 206 base::Bind(&InvokeCallbackOnNthInvocation, 207 base::Owned(new int(origin_to_contexts_.size())), callback); 208 for (OriginToContext::iterator iter = origin_to_contexts_.begin(); 209 iter != origin_to_contexts_.end(); ++iter) 210 sync_context_->PromoteDemotedChanges(iter->first, iter->second, 211 completion_callback); 212} 213 214void LocalFileSyncService::GetLocalFileMetadata( 215 const FileSystemURL& url, const SyncFileMetadataCallback& callback) { 216 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 217 sync_context_->GetFileMetadata(origin_to_contexts_[url.origin()], 218 url, callback); 219} 220 221void LocalFileSyncService::PrepareForProcessRemoteChange( 222 const FileSystemURL& url, 223 const PrepareChangeCallback& callback) { 224 DVLOG(1) << "PrepareForProcessRemoteChange: " << url.DebugString(); 225 226 if (!ContainsKey(origin_to_contexts_, url.origin())) { 227 // This could happen if a remote sync is triggered for the app that hasn't 228 // been initialized in this service. 229 DCHECK(profile_); 230 // The given url.origin() must be for valid installed app. 231 const extensions::Extension* extension = 232 extensions::ExtensionRegistry::Get(profile_) 233 ->enabled_extensions().GetAppByURL(url.origin()); 234 if (!extension) { 235 util::Log( 236 logging::LOG_WARNING, 237 FROM_HERE, 238 "PrepareForProcessRemoteChange called for non-existing origin: %s", 239 url.origin().spec().c_str()); 240 241 // The extension has been uninstalled and this method is called 242 // before the remote changes for the origin are removed. 243 callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC, 244 SyncFileMetadata(), FileChangeList()); 245 return; 246 } 247 GURL site_url = 248 extensions::util::GetSiteForExtensionId(extension->id(), profile_); 249 DCHECK(!site_url.is_empty()); 250 scoped_refptr<storage::FileSystemContext> file_system_context = 251 content::BrowserContext::GetStoragePartitionForSite(profile_, site_url) 252 ->GetFileSystemContext(); 253 MaybeInitializeFileSystemContext( 254 url.origin(), 255 file_system_context.get(), 256 base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync, 257 AsWeakPtr(), 258 url, 259 file_system_context, 260 callback)); 261 return; 262 } 263 264 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 265 sync_context_->PrepareForSync( 266 origin_to_contexts_[url.origin()], url, 267 LocalFileSyncContext::SYNC_EXCLUSIVE, 268 base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter, callback)); 269} 270 271void LocalFileSyncService::ApplyRemoteChange( 272 const FileChange& change, 273 const base::FilePath& local_path, 274 const FileSystemURL& url, 275 const SyncStatusCallback& callback) { 276 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 277 util::Log(logging::LOG_VERBOSE, FROM_HERE, 278 "[Remote -> Local] ApplyRemoteChange: %s on %s", 279 change.DebugString().c_str(), 280 url.DebugString().c_str()); 281 282 sync_context_->ApplyRemoteChange( 283 origin_to_contexts_[url.origin()], 284 change, local_path, url, 285 base::Bind(&LocalFileSyncService::DidApplyRemoteChange, AsWeakPtr(), 286 callback)); 287} 288 289void LocalFileSyncService::FinalizeRemoteSync( 290 const FileSystemURL& url, 291 bool clear_local_changes, 292 const base::Closure& completion_callback) { 293 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 294 sync_context_->FinalizeExclusiveSync( 295 origin_to_contexts_[url.origin()], 296 url, clear_local_changes, completion_callback); 297} 298 299void LocalFileSyncService::RecordFakeLocalChange( 300 const FileSystemURL& url, 301 const FileChange& change, 302 const SyncStatusCallback& callback) { 303 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 304 sync_context_->RecordFakeLocalChange(origin_to_contexts_[url.origin()], 305 url, change, callback); 306} 307 308void LocalFileSyncService::OnChangesAvailableInOrigins( 309 const std::set<GURL>& origins) { 310 bool need_notification = false; 311 for (std::set<GURL>::const_iterator iter = origins.begin(); 312 iter != origins.end(); ++iter) { 313 const GURL& origin = *iter; 314 if (!ContainsKey(origin_to_contexts_, origin)) { 315 // This could happen if this is called for apps/origins that haven't 316 // been initialized yet, or for apps/origins that are disabled. 317 // (Local change tracker could call this for uninitialized origins 318 // while it's reading dirty files from the database in the 319 // initialization phase.) 320 pending_origins_with_changes_.insert(origin); 321 continue; 322 } 323 need_notification = true; 324 SyncFileSystemBackend* backend = 325 SyncFileSystemBackend::GetBackend(origin_to_contexts_[origin]); 326 DCHECK(backend); 327 DCHECK(backend->change_tracker()); 328 origin_change_map_.SetOriginChangeCount( 329 origin, backend->change_tracker()->num_changes()); 330 } 331 if (!need_notification) 332 return; 333 int64 num_changes = origin_change_map_.GetTotalChangeCount(); 334 FOR_EACH_OBSERVER(Observer, change_observers_, 335 OnLocalChangeAvailable(num_changes)); 336} 337 338void LocalFileSyncService::SetOriginEnabled(const GURL& origin, bool enabled) { 339 if (!ContainsKey(origin_to_contexts_, origin)) 340 return; 341 origin_change_map_.SetOriginEnabled(origin, enabled); 342} 343 344LocalFileSyncService::LocalFileSyncService(Profile* profile, 345 leveldb::Env* env_override) 346 : profile_(profile), 347 sync_context_(new LocalFileSyncContext( 348 profile_->GetPath(), 349 env_override, 350 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI).get(), 351 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO) 352 .get())), 353 local_change_processor_(NULL) { 354 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 355 sync_context_->AddOriginChangeObserver(this); 356} 357 358void LocalFileSyncService::DidInitializeFileSystemContext( 359 const GURL& app_origin, 360 storage::FileSystemContext* file_system_context, 361 const SyncStatusCallback& callback, 362 SyncStatusCode status) { 363 if (status != SYNC_STATUS_OK) { 364 callback.Run(status); 365 return; 366 } 367 DCHECK(file_system_context); 368 origin_to_contexts_[app_origin] = file_system_context; 369 370 if (pending_origins_with_changes_.find(app_origin) != 371 pending_origins_with_changes_.end()) { 372 // We have remaining changes for the origin. 373 pending_origins_with_changes_.erase(app_origin); 374 SyncFileSystemBackend* backend = 375 SyncFileSystemBackend::GetBackend(file_system_context); 376 DCHECK(backend); 377 DCHECK(backend->change_tracker()); 378 origin_change_map_.SetOriginChangeCount( 379 app_origin, backend->change_tracker()->num_changes()); 380 int64 num_changes = origin_change_map_.GetTotalChangeCount(); 381 FOR_EACH_OBSERVER(Observer, change_observers_, 382 OnLocalChangeAvailable(num_changes)); 383 } 384 callback.Run(status); 385} 386 387void LocalFileSyncService::DidInitializeForRemoteSync( 388 const FileSystemURL& url, 389 storage::FileSystemContext* file_system_context, 390 const PrepareChangeCallback& callback, 391 SyncStatusCode status) { 392 if (status != SYNC_STATUS_OK) { 393 DVLOG(1) << "FileSystemContext initialization failed for remote sync:" 394 << url.DebugString() << " status=" << status 395 << " (" << SyncStatusCodeToString(status) << ")"; 396 callback.Run(status, SyncFileMetadata(), FileChangeList()); 397 return; 398 } 399 origin_to_contexts_[url.origin()] = file_system_context; 400 PrepareForProcessRemoteChange(url, callback); 401} 402 403void LocalFileSyncService::RunLocalSyncCallback( 404 SyncStatusCode status, 405 const FileSystemURL& url) { 406 DVLOG(1) << "Local sync is finished with: " << status 407 << " on " << url.DebugString(); 408 DCHECK(!local_sync_callback_.is_null()); 409 SyncFileCallback callback = local_sync_callback_; 410 local_sync_callback_.Reset(); 411 callback.Run(status, url); 412} 413 414void LocalFileSyncService::DidApplyRemoteChange( 415 const SyncStatusCallback& callback, 416 SyncStatusCode status) { 417 util::Log(logging::LOG_VERBOSE, FROM_HERE, 418 "[Remote -> Local] ApplyRemoteChange finished --> %s", 419 SyncStatusCodeToString(status)); 420 callback.Run(status); 421} 422 423void LocalFileSyncService::DidGetFileForLocalSync( 424 SyncStatusCode status, 425 const LocalFileSyncInfo& sync_file_info, 426 storage::ScopedFile snapshot) { 427 DCHECK(!local_sync_callback_.is_null()); 428 if (status != SYNC_STATUS_OK) { 429 RunLocalSyncCallback(status, sync_file_info.url); 430 return; 431 } 432 if (sync_file_info.changes.empty()) { 433 // There's a slight chance this could happen. 434 SyncFileCallback callback = local_sync_callback_; 435 local_sync_callback_.Reset(); 436 ProcessLocalChange(callback); 437 return; 438 } 439 440 FileChange next_change = sync_file_info.changes.front(); 441 DVLOG(1) << "ProcessLocalChange: " << sync_file_info.url.DebugString() 442 << " change:" << next_change.DebugString(); 443 444 GetLocalChangeProcessor(sync_file_info.url)->ApplyLocalChange( 445 next_change, 446 sync_file_info.local_file_path, 447 sync_file_info.metadata, 448 sync_file_info.url, 449 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL, 450 AsWeakPtr(), base::Passed(&snapshot), sync_file_info, 451 next_change, sync_file_info.changes.PopAndGetNewList())); 452} 453 454void LocalFileSyncService::ProcessNextChangeForURL( 455 storage::ScopedFile snapshot, 456 const LocalFileSyncInfo& sync_file_info, 457 const FileChange& processed_change, 458 const FileChangeList& changes, 459 SyncStatusCode status) { 460 DVLOG(1) << "Processed one local change: " 461 << sync_file_info.url.DebugString() 462 << " change:" << processed_change.DebugString() 463 << " status:" << status; 464 465 if (status == SYNC_STATUS_RETRY) { 466 GetLocalChangeProcessor(sync_file_info.url)->ApplyLocalChange( 467 processed_change, 468 sync_file_info.local_file_path, 469 sync_file_info.metadata, 470 sync_file_info.url, 471 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL, 472 AsWeakPtr(), base::Passed(&snapshot), 473 sync_file_info, processed_change, changes)); 474 return; 475 } 476 477 if (status == SYNC_FILE_ERROR_NOT_FOUND && 478 processed_change.change() == FileChange::FILE_CHANGE_DELETE) { 479 // This must be ok (and could happen). 480 status = SYNC_STATUS_OK; 481 } 482 483 const FileSystemURL& url = sync_file_info.url; 484 if (status != SYNC_STATUS_OK || changes.empty()) { 485 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 486 sync_context_->FinalizeSnapshotSync( 487 origin_to_contexts_[url.origin()], url, status, 488 base::Bind(&LocalFileSyncService::RunLocalSyncCallback, 489 AsWeakPtr(), status, url)); 490 return; 491 } 492 493 FileChange next_change = changes.front(); 494 GetLocalChangeProcessor(url)->ApplyLocalChange( 495 changes.front(), 496 sync_file_info.local_file_path, 497 sync_file_info.metadata, 498 url, 499 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL, 500 AsWeakPtr(), base::Passed(&snapshot), sync_file_info, 501 next_change, changes.PopAndGetNewList())); 502} 503 504LocalChangeProcessor* LocalFileSyncService::GetLocalChangeProcessor( 505 const FileSystemURL& url) { 506 if (!get_local_change_processor_.is_null()) 507 return get_local_change_processor_.Run(url.origin()); 508 DCHECK(local_change_processor_); 509 return local_change_processor_; 510} 511 512} // namespace sync_file_system 513