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