local_file_sync_service.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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#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
99LocalFileSyncService::LocalFileSyncService(Profile* profile)
100    : profile_(profile),
101      sync_context_(new LocalFileSyncContext(
102          profile_->GetPath(),
103          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI).get(),
104          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)
105              .get())),
106      local_change_processor_(NULL) {
107  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108  sync_context_->AddOriginChangeObserver(this);
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  DCHECK(local_change_processor_);
145  // Pick an origin to process next.
146  GURL origin;
147  if (!origin_change_map_.NextOriginToProcess(&origin)) {
148    callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC, FileSystemURL());
149    return;
150  }
151  DCHECK(local_sync_callback_.is_null());
152  DCHECK(!origin.is_empty());
153  DCHECK(ContainsKey(origin_to_contexts_, origin));
154
155  DVLOG(1) << "Starting ProcessLocalChange";
156
157  local_sync_callback_ = callback;
158
159  sync_context_->GetFileForLocalSync(
160      origin_to_contexts_[origin],
161      base::Bind(&LocalFileSyncService::DidGetFileForLocalSync,
162                 AsWeakPtr()));
163}
164
165void LocalFileSyncService::SetLocalChangeProcessor(
166    LocalChangeProcessor* processor) {
167  local_change_processor_ = processor;
168}
169
170void LocalFileSyncService::HasPendingLocalChanges(
171    const FileSystemURL& url,
172    const HasPendingLocalChangeCallback& callback) {
173  if (!ContainsKey(origin_to_contexts_, url.origin())) {
174    base::MessageLoopProxy::current()->PostTask(
175        FROM_HERE,
176        base::Bind(callback, SYNC_FILE_ERROR_INVALID_URL, false));
177    return;
178  }
179  sync_context_->HasPendingLocalChanges(
180      origin_to_contexts_[url.origin()], url, callback);
181}
182
183void LocalFileSyncService::GetLocalFileMetadata(
184    const FileSystemURL& url, const SyncFileMetadataCallback& callback) {
185  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
186  sync_context_->GetFileMetadata(origin_to_contexts_[url.origin()],
187                                 url, callback);
188}
189
190void LocalFileSyncService::PrepareForProcessRemoteChange(
191    const FileSystemURL& url,
192    const PrepareChangeCallback& callback) {
193  DVLOG(1) << "PrepareForProcessRemoteChange: " << url.DebugString();
194
195  if (!ContainsKey(origin_to_contexts_, url.origin())) {
196    // This could happen if a remote sync is triggered for the app that hasn't
197    // been initialized in this service.
198    DCHECK(profile_);
199    // The given url.origin() must be for valid installed app.
200    ExtensionService* extension_service =
201        extensions::ExtensionSystem::Get(profile_)->extension_service();
202    const extensions::Extension* extension = extension_service->GetInstalledApp(
203        url.origin());
204    if (!extension) {
205      util::Log(
206          logging::LOG_WARNING,
207          FROM_HERE,
208          "PrepareForProcessRemoteChange called for non-existing origin: %s",
209          url.origin().spec().c_str());
210
211      // The extension has been uninstalled and this method is called
212      // before the remote changes for the origin are removed.
213      callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC,
214                   SyncFileMetadata(), FileChangeList());
215      return;
216    }
217    GURL site_url = extension_service->GetSiteForExtensionId(extension->id());
218    DCHECK(!site_url.is_empty());
219    scoped_refptr<fileapi::FileSystemContext> file_system_context =
220        content::BrowserContext::GetStoragePartitionForSite(
221            profile_, site_url)->GetFileSystemContext();
222    MaybeInitializeFileSystemContext(
223        url.origin(),
224        file_system_context.get(),
225        base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync,
226                   AsWeakPtr(),
227                   url,
228                   file_system_context,
229                   callback));
230    return;
231  }
232
233  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
234  sync_context_->PrepareForSync(
235      origin_to_contexts_[url.origin()], url,
236      LocalFileSyncContext::SYNC_EXCLUSIVE,
237      base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter, callback));
238}
239
240void LocalFileSyncService::ApplyRemoteChange(
241    const FileChange& change,
242    const base::FilePath& local_path,
243    const FileSystemURL& url,
244    const SyncStatusCallback& callback) {
245  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
246  sync_context_->ApplyRemoteChange(
247      origin_to_contexts_[url.origin()],
248      change, local_path, url, callback);
249}
250
251void LocalFileSyncService::FinalizeRemoteSync(
252    const FileSystemURL& url,
253    bool clear_local_changes,
254    const base::Closure& completion_callback) {
255  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
256  sync_context_->FinalizeExclusiveSync(
257      origin_to_contexts_[url.origin()],
258      url, clear_local_changes, completion_callback);
259}
260
261void 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
270void 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
300void 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
306void 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
335void 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
351void 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
362void LocalFileSyncService::DidGetFileForLocalSync(
363    SyncStatusCode status,
364    const LocalFileSyncInfo& sync_file_info,
365    webkit_blob::ScopedFile snapshot) {
366  DCHECK(!local_sync_callback_.is_null());
367  if (status != SYNC_STATUS_OK) {
368    RunLocalSyncCallback(status, sync_file_info.url);
369    return;
370  }
371  if (sync_file_info.changes.empty()) {
372    // There's a slight chance this could happen.
373    SyncFileCallback callback = local_sync_callback_;
374    local_sync_callback_.Reset();
375    ProcessLocalChange(callback);
376    return;
377  }
378
379  FileChange next_change = sync_file_info.changes.front();
380  DVLOG(1) << "ProcessLocalChange: " << sync_file_info.url.DebugString()
381           << " change:" << next_change.DebugString();
382
383  local_change_processor_->ApplyLocalChange(
384      next_change,
385      sync_file_info.local_file_path,
386      sync_file_info.metadata,
387      sync_file_info.url,
388      base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
389                 AsWeakPtr(), base::Passed(&snapshot), sync_file_info,
390                 next_change, sync_file_info.changes.PopAndGetNewList()));
391}
392
393void LocalFileSyncService::ProcessNextChangeForURL(
394    webkit_blob::ScopedFile snapshot,
395    const LocalFileSyncInfo& sync_file_info,
396    const FileChange& processed_change,
397    const FileChangeList& changes,
398    SyncStatusCode status) {
399  DVLOG(1) << "Processed one local change: "
400           << sync_file_info.url.DebugString()
401           << " change:" << processed_change.DebugString()
402           << " status:" << status;
403
404  if (status == SYNC_STATUS_RETRY) {
405    local_change_processor_->ApplyLocalChange(
406        processed_change,
407        sync_file_info.local_file_path,
408        sync_file_info.metadata,
409        sync_file_info.url,
410        base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
411                   AsWeakPtr(), base::Passed(&snapshot),
412                   sync_file_info, processed_change, changes));
413    return;
414  }
415
416  if (status == SYNC_FILE_ERROR_NOT_FOUND &&
417      processed_change.change() == FileChange::FILE_CHANGE_DELETE) {
418    // This must be ok (and could happen).
419    status = SYNC_STATUS_OK;
420  }
421
422  const FileSystemURL& url = sync_file_info.url;
423  if (status != SYNC_STATUS_OK || changes.empty()) {
424    DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
425    sync_context_->FinalizeSnapshotSync(
426        origin_to_contexts_[url.origin()], url, status,
427        base::Bind(&LocalFileSyncService::RunLocalSyncCallback,
428                   AsWeakPtr(), status, url));
429    return;
430  }
431
432  FileChange next_change = changes.front();
433  local_change_processor_->ApplyLocalChange(
434      changes.front(),
435      sync_file_info.local_file_path,
436      sync_file_info.metadata,
437      url,
438      base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
439                 AsWeakPtr(), base::Passed(&snapshot), sync_file_info,
440                 next_change, changes.PopAndGetNewList()));
441}
442
443}  // namespace sync_file_system
444