local_file_sync_service.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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    scoped_ptr<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::ClearSyncFlagForURL(
184    const FileSystemURL& url) {
185  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
186  sync_context_->ClearSyncFlagForURL(url);
187}
188
189void LocalFileSyncService::GetLocalFileMetadata(
190    const FileSystemURL& url, const SyncFileMetadataCallback& callback) {
191  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
192  sync_context_->GetFileMetadata(origin_to_contexts_[url.origin()],
193                                 url, callback);
194}
195
196void LocalFileSyncService::PrepareForProcessRemoteChange(
197    const FileSystemURL& url,
198    const PrepareChangeCallback& callback) {
199  DVLOG(1) << "PrepareForProcessRemoteChange: " << url.DebugString();
200
201  if (!ContainsKey(origin_to_contexts_, url.origin())) {
202    // This could happen if a remote sync is triggered for the app that hasn't
203    // been initialized in this service.
204    DCHECK(profile_);
205    // The given url.origin() must be for valid installed app.
206    ExtensionService* extension_service =
207        extensions::ExtensionSystem::Get(profile_)->extension_service();
208    const extensions::Extension* extension = extension_service->GetInstalledApp(
209        url.origin());
210    if (!extension) {
211      util::Log(
212          logging::LOG_WARNING,
213          FROM_HERE,
214          "PrepareForProcessRemoteChange called for non-existing origin: %s",
215          url.origin().spec().c_str());
216
217      // The extension has been uninstalled and this method is called
218      // before the remote changes for the origin are removed.
219      callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC,
220                   SyncFileMetadata(), FileChangeList());
221      return;
222    }
223    GURL site_url = extension_service->GetSiteForExtensionId(extension->id());
224    DCHECK(!site_url.is_empty());
225    scoped_refptr<fileapi::FileSystemContext> file_system_context =
226        content::BrowserContext::GetStoragePartitionForSite(
227            profile_, site_url)->GetFileSystemContext();
228    MaybeInitializeFileSystemContext(
229        url.origin(),
230        file_system_context.get(),
231        base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync,
232                   AsWeakPtr(),
233                   url,
234                   file_system_context,
235                   callback));
236    return;
237  }
238
239  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
240  sync_context_->PrepareForSync(
241      origin_to_contexts_[url.origin()], url,
242      LocalFileSyncContext::SYNC_EXCLUSIVE,
243      base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter, callback));
244}
245
246void LocalFileSyncService::ApplyRemoteChange(
247    const FileChange& change,
248    const base::FilePath& local_path,
249    const FileSystemURL& url,
250    const SyncStatusCallback& callback) {
251  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
252  sync_context_->ApplyRemoteChange(
253      origin_to_contexts_[url.origin()],
254      change, local_path, url, callback);
255}
256
257void LocalFileSyncService::ClearLocalChanges(
258    const FileSystemURL& url,
259    const base::Closure& completion_callback) {
260  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
261  sync_context_->ClearChangesForURL(origin_to_contexts_[url.origin()],
262                                    url, completion_callback);
263}
264
265void LocalFileSyncService::RecordFakeLocalChange(
266    const FileSystemURL& url,
267    const FileChange& change,
268    const SyncStatusCallback& callback) {
269  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
270  sync_context_->RecordFakeLocalChange(origin_to_contexts_[url.origin()],
271                                       url, change, callback);
272}
273
274void LocalFileSyncService::OnChangesAvailableInOrigins(
275    const std::set<GURL>& origins) {
276  bool need_notification = false;
277  for (std::set<GURL>::const_iterator iter = origins.begin();
278       iter != origins.end(); ++iter) {
279    const GURL& origin = *iter;
280    if (!ContainsKey(origin_to_contexts_, origin)) {
281      // This could happen if this is called for apps/origins that haven't
282      // been initialized yet, or for apps/origins that are disabled.
283      // (Local change tracker could call this for uninitialized origins
284      // while it's reading dirty files from the database in the
285      // initialization phase.)
286      pending_origins_with_changes_.insert(origin);
287      continue;
288    }
289    need_notification = true;
290    SyncFileSystemBackend* backend =
291        SyncFileSystemBackend::GetBackend(origin_to_contexts_[origin]);
292    DCHECK(backend);
293    DCHECK(backend->change_tracker());
294    origin_change_map_.SetOriginChangeCount(
295        origin, backend->change_tracker()->num_changes());
296  }
297  if (!need_notification)
298    return;
299  int64 num_changes = origin_change_map_.GetTotalChangeCount();
300  FOR_EACH_OBSERVER(Observer, change_observers_,
301                    OnLocalChangeAvailable(num_changes));
302}
303
304void LocalFileSyncService::SetOriginEnabled(const GURL& origin, bool enabled) {
305  if (!ContainsKey(origin_to_contexts_, origin))
306    return;
307  origin_change_map_.SetOriginEnabled(origin, enabled);
308}
309
310void LocalFileSyncService::DidInitializeFileSystemContext(
311    const GURL& app_origin,
312    fileapi::FileSystemContext* file_system_context,
313    const SyncStatusCallback& callback,
314    SyncStatusCode status) {
315  if (status != SYNC_STATUS_OK) {
316    callback.Run(status);
317    return;
318  }
319  DCHECK(file_system_context);
320  origin_to_contexts_[app_origin] = file_system_context;
321
322  if (pending_origins_with_changes_.find(app_origin) !=
323      pending_origins_with_changes_.end()) {
324    // We have remaining changes for the origin.
325    pending_origins_with_changes_.erase(app_origin);
326    SyncFileSystemBackend* backend =
327        SyncFileSystemBackend::GetBackend(file_system_context);
328    DCHECK(backend);
329    DCHECK(backend->change_tracker());
330    origin_change_map_.SetOriginChangeCount(
331        app_origin, backend->change_tracker()->num_changes());
332    int64 num_changes = origin_change_map_.GetTotalChangeCount();
333    FOR_EACH_OBSERVER(Observer, change_observers_,
334                      OnLocalChangeAvailable(num_changes));
335  }
336  callback.Run(status);
337}
338
339void LocalFileSyncService::DidInitializeForRemoteSync(
340    const FileSystemURL& url,
341    fileapi::FileSystemContext* file_system_context,
342    const PrepareChangeCallback& callback,
343    SyncStatusCode status) {
344  if (status != SYNC_STATUS_OK) {
345    DVLOG(1) << "FileSystemContext initialization failed for remote sync:"
346             << url.DebugString() << " status=" << status
347             << " (" << SyncStatusCodeToString(status) << ")";
348    callback.Run(status, SyncFileMetadata(), FileChangeList());
349    return;
350  }
351  origin_to_contexts_[url.origin()] = file_system_context;
352  PrepareForProcessRemoteChange(url, callback);
353}
354
355void LocalFileSyncService::RunLocalSyncCallback(
356    SyncStatusCode status,
357    const FileSystemURL& url) {
358  DVLOG(1) << "Local sync is finished with: " << status
359           << " on " << url.DebugString();
360  DCHECK(!local_sync_callback_.is_null());
361  SyncFileCallback callback = local_sync_callback_;
362  local_sync_callback_.Reset();
363  callback.Run(status, url);
364}
365
366void LocalFileSyncService::DidGetFileForLocalSync(
367    SyncStatusCode status,
368    const LocalFileSyncInfo& sync_file_info,
369    scoped_ptr<webkit_blob::ScopedFile> snapshot) {
370  DCHECK(!local_sync_callback_.is_null());
371  if (status != SYNC_STATUS_OK) {
372    RunLocalSyncCallback(status, sync_file_info.url);
373    return;
374  }
375  if (sync_file_info.changes.empty()) {
376    // There's a slight chance this could happen.
377    SyncFileCallback callback = local_sync_callback_;
378    local_sync_callback_.Reset();
379    ProcessLocalChange(callback);
380    return;
381  }
382
383  FileChange next_change = sync_file_info.changes.front();
384  DVLOG(1) << "ProcessLocalChange: " << sync_file_info.url.DebugString()
385           << " change:" << next_change.DebugString();
386
387  local_change_processor_->ApplyLocalChange(
388      next_change,
389      sync_file_info.local_file_path,
390      sync_file_info.metadata,
391      sync_file_info.url,
392      base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
393                 AsWeakPtr(), base::Passed(&snapshot), sync_file_info,
394                 next_change, sync_file_info.changes.PopAndGetNewList()));
395}
396
397void LocalFileSyncService::ProcessNextChangeForURL(
398    scoped_ptr<webkit_blob::ScopedFile> snapshot,
399    const LocalFileSyncInfo& sync_file_info,
400    const FileChange& processed_change,
401    const FileChangeList& changes,
402    SyncStatusCode status) {
403  DVLOG(1) << "Processed one local change: "
404           << sync_file_info.url.DebugString()
405           << " change:" << processed_change.DebugString()
406           << " status:" << status;
407
408  if (status == SYNC_STATUS_RETRY) {
409    local_change_processor_->ApplyLocalChange(
410        processed_change,
411        sync_file_info.local_file_path,
412        sync_file_info.metadata,
413        sync_file_info.url,
414        base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
415                   AsWeakPtr(), base::Passed(&snapshot),
416                   sync_file_info, processed_change, changes));
417    return;
418  }
419
420  if (status == SYNC_FILE_ERROR_NOT_FOUND &&
421      processed_change.change() == FileChange::FILE_CHANGE_DELETE) {
422    // This must be ok (and could happen).
423    status = SYNC_STATUS_OK;
424  }
425
426  const FileSystemURL& url = sync_file_info.url;
427  if (status != SYNC_STATUS_OK || changes.empty()) {
428    DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
429    sync_context_->CommitChangeStatusForURL(
430        origin_to_contexts_[url.origin()], url, status,
431        base::Bind(&LocalFileSyncService::RunLocalSyncCallback,
432                  AsWeakPtr(), status, url));
433    return;
434  }
435
436  FileChange next_change = changes.front();
437  local_change_processor_->ApplyLocalChange(
438      changes.front(),
439      sync_file_info.local_file_path,
440      sync_file_info.metadata,
441      url,
442      base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
443                 AsWeakPtr(), base::Passed(&snapshot), sync_file_info,
444                 next_change, changes.PopAndGetNewList()));
445}
446
447}  // namespace sync_file_system
448