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