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