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/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