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 26 using content::BrowserThread; 27 using fileapi::FileSystemURL; 28 29 namespace sync_file_system { 30 31 namespace { 32 33 void 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 42 LocalFileSyncService::OriginChangeMap::OriginChangeMap() 43 : next_(change_count_map_.end()) {} 44 LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {} 45 46 bool 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 62 int64 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 73 void 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 87 void 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 97 LocalFileSyncService::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 108 LocalFileSyncService::~LocalFileSyncService() { 109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 110 } 111 112 void LocalFileSyncService::Shutdown() { 113 sync_context_->RemoveOriginChangeObserver(this); 114 sync_context_->ShutdownOnUIThread(); 115 profile_ = NULL; 116 } 117 118 void 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 129 void LocalFileSyncService::AddChangeObserver(Observer* observer) { 130 change_observers_.AddObserver(observer); 131 } 132 133 void LocalFileSyncService::RegisterURLForWaitingSync( 134 const FileSystemURL& url, 135 const base::Closure& on_syncable_callback) { 136 sync_context_->RegisterURLForWaitingSync(url, on_syncable_callback); 137 } 138 139 void 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 162 void LocalFileSyncService::SetLocalChangeProcessor( 163 LocalChangeProcessor* processor) { 164 local_change_processor_ = processor; 165 } 166 167 void 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 180 void LocalFileSyncService::ClearSyncFlagForURL( 181 const FileSystemURL& url) { 182 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 183 sync_context_->ClearSyncFlagForURL(url); 184 } 185 186 void 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 193 void 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 242 void 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 253 void 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 261 void 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 270 void 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 300 void 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 306 void 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 335 void 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 351 void 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 362 void 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 DVLOG(1) << "ProcessLocalChange: " << sync_file_info.url.DebugString() 379 << " change:" << sync_file_info.changes.front().DebugString(); 380 381 local_change_processor_->ApplyLocalChange( 382 sync_file_info.changes.front(), 383 sync_file_info.local_file_path, 384 sync_file_info.metadata, 385 sync_file_info.url, 386 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL, 387 AsWeakPtr(), 388 sync_file_info, 389 sync_file_info.changes.front(), 390 sync_file_info.changes.PopAndGetNewList())); 391 } 392 393 void LocalFileSyncService::ProcessNextChangeForURL( 394 const LocalFileSyncInfo& sync_file_info, 395 const FileChange& last_change, 396 const FileChangeList& changes, 397 SyncStatusCode status) { 398 DVLOG(1) << "Processed one local change: " 399 << sync_file_info.url.DebugString() 400 << " change:" << last_change.DebugString() 401 << " status:" << status; 402 403 if (status == SYNC_FILE_ERROR_NOT_FOUND && 404 last_change.change() == FileChange::FILE_CHANGE_DELETE) { 405 // This must be ok (and could happen). 406 status = SYNC_STATUS_OK; 407 } 408 409 // TODO(kinuko,tzik): Handle other errors that should not be considered 410 // a sync error. 411 412 const FileSystemURL& url = sync_file_info.url; 413 if (status != SYNC_STATUS_OK || changes.empty()) { 414 if (status == SYNC_STATUS_OK || status == SYNC_STATUS_HAS_CONFLICT) { 415 // Clear the recorded changes for the URL if the sync was successfull 416 // OR has failed due to conflict (so that we won't stick to the same 417 // conflicting file again and again). 418 DCHECK(ContainsKey(origin_to_contexts_, url.origin())); 419 sync_context_->ClearChangesForURL( 420 origin_to_contexts_[url.origin()], url, 421 base::Bind(&LocalFileSyncService::RunLocalSyncCallback, 422 AsWeakPtr(), status, url)); 423 return; 424 } 425 RunLocalSyncCallback(status, url); 426 return; 427 } 428 429 local_change_processor_->ApplyLocalChange( 430 changes.front(), 431 sync_file_info.local_file_path, 432 sync_file_info.metadata, 433 url, 434 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL, 435 AsWeakPtr(), sync_file_info, 436 changes.front(), changes.PopAndGetNewList())); 437 } 438 439 } // namespace sync_file_system 440