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