local_file_sync_service.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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 storage::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    storage::ScopedFile snapshot) {
42  callback.Run(status, sync_file_info.metadata, sync_file_info.changes);
43}
44
45void InvokeCallbackOnNthInvocation(int* count, const base::Closure& callback) {
46  --*count;
47  if (*count <= 0)
48    callback.Run();
49}
50
51}  // namespace
52
53LocalFileSyncService::OriginChangeMap::OriginChangeMap()
54    : next_(change_count_map_.end()) {}
55LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {}
56
57bool 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
73int64 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
84void 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
98void 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
108scoped_ptr<LocalFileSyncService> LocalFileSyncService::Create(
109    Profile* profile) {
110  return make_scoped_ptr(new LocalFileSyncService(profile, NULL));
111}
112
113scoped_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
122LocalFileSyncService::~LocalFileSyncService() {
123  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
124}
125
126void LocalFileSyncService::Shutdown() {
127  sync_context_->RemoveOriginChangeObserver(this);
128  sync_context_->ShutdownOnUIThread();
129  profile_ = NULL;
130}
131
132void 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
143void LocalFileSyncService::AddChangeObserver(Observer* observer) {
144  change_observers_.AddObserver(observer);
145}
146
147void LocalFileSyncService::RegisterURLForWaitingSync(
148    const FileSystemURL& url,
149    const base::Closure& on_syncable_callback) {
150  sync_context_->RegisterURLForWaitingSync(url, on_syncable_callback);
151}
152
153void 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
175void LocalFileSyncService::SetLocalChangeProcessor(
176    LocalChangeProcessor* local_change_processor) {
177  local_change_processor_ = local_change_processor;
178}
179
180void LocalFileSyncService::SetLocalChangeProcessorCallback(
181    const GetLocalChangeProcessorCallback& get_local_change_processor) {
182  get_local_change_processor_ = get_local_change_processor;
183}
184
185void 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
198void 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
214void 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
221void 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
271void 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
289void 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
299void 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
308void 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
338void 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
344LocalFileSyncService::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
358void 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
387void 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
403void 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
414void 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
423void 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
454void 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
504LocalChangeProcessor* 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