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