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