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