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