local_file_sync_service.cc revision 2385ea399aae016c0806a4f9ef3c9cfe3d2a39df
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
26using content::BrowserThread;
27using fileapi::FileSystemURL;
28
29namespace sync_file_system {
30
31namespace {
32
33void PrepareForProcessRemoteChangeCallbackAdapter(
34    const RemoteChangeProcessor::PrepareChangeCallback& callback,
35    SyncStatusCode status,
36    const LocalFileSyncInfo& sync_file_info) {
37  callback.Run(status, sync_file_info.metadata, sync_file_info.changes);
38}
39
40}  // namespace
41
42LocalFileSyncService::OriginChangeMap::OriginChangeMap()
43    : next_(change_count_map_.end()) {}
44LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {}
45
46bool LocalFileSyncService::OriginChangeMap::NextOriginToProcess(GURL* origin) {
47  DCHECK(origin);
48  if (change_count_map_.empty())
49    return false;
50  Map::iterator begin = next_;
51  do {
52    if (next_ == change_count_map_.end())
53      next_ = change_count_map_.begin();
54    DCHECK_NE(0, next_->second);
55    *origin = next_++->first;
56    if (!ContainsKey(disabled_origins_, *origin))
57      return true;
58  } while (next_ != begin);
59  return false;
60}
61
62int64 LocalFileSyncService::OriginChangeMap::GetTotalChangeCount() const {
63  int64 num_changes = 0;
64  for (Map::const_iterator iter = change_count_map_.begin();
65       iter != change_count_map_.end(); ++iter) {
66    if (ContainsKey(disabled_origins_, iter->first))
67      continue;
68    num_changes += iter->second;
69  }
70  return num_changes;
71}
72
73void LocalFileSyncService::OriginChangeMap::SetOriginChangeCount(
74    const GURL& origin, int64 changes) {
75  if (changes != 0) {
76    change_count_map_[origin] = changes;
77    return;
78  }
79  Map::iterator found = change_count_map_.find(origin);
80  if (found != change_count_map_.end()) {
81    if (next_ == found)
82      ++next_;
83    change_count_map_.erase(found);
84  }
85}
86
87void LocalFileSyncService::OriginChangeMap::SetOriginEnabled(
88    const GURL& origin, bool enabled) {
89  if (enabled)
90    disabled_origins_.erase(origin);
91  else
92    disabled_origins_.insert(origin);
93}
94
95// LocalFileSyncService -------------------------------------------------------
96
97LocalFileSyncService::LocalFileSyncService(Profile* profile)
98    : profile_(profile),
99      sync_context_(new LocalFileSyncContext(
100          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI).get(),
101          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)
102              .get())),
103      local_change_processor_(NULL) {
104  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
105  sync_context_->AddOriginChangeObserver(this);
106}
107
108LocalFileSyncService::~LocalFileSyncService() {
109  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
110}
111
112void LocalFileSyncService::Shutdown() {
113  sync_context_->RemoveOriginChangeObserver(this);
114  sync_context_->ShutdownOnUIThread();
115  profile_ = NULL;
116}
117
118void LocalFileSyncService::MaybeInitializeFileSystemContext(
119    const GURL& app_origin,
120    fileapi::FileSystemContext* file_system_context,
121    const SyncStatusCallback& callback) {
122  sync_context_->MaybeInitializeFileSystemContext(
123      app_origin, file_system_context,
124      base::Bind(&LocalFileSyncService::DidInitializeFileSystemContext,
125                 AsWeakPtr(), app_origin,
126                 make_scoped_refptr(file_system_context), callback));
127}
128
129void LocalFileSyncService::AddChangeObserver(Observer* observer) {
130  change_observers_.AddObserver(observer);
131}
132
133void LocalFileSyncService::RegisterURLForWaitingSync(
134    const FileSystemURL& url,
135    const base::Closure& on_syncable_callback) {
136  sync_context_->RegisterURLForWaitingSync(url, on_syncable_callback);
137}
138
139void LocalFileSyncService::ProcessLocalChange(
140    const SyncFileCallback& callback) {
141  DCHECK(local_change_processor_);
142  // Pick an origin to process next.
143  GURL origin;
144  if (!origin_change_map_.NextOriginToProcess(&origin)) {
145    callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC, FileSystemURL());
146    return;
147  }
148  DCHECK(local_sync_callback_.is_null());
149  DCHECK(!origin.is_empty());
150  DCHECK(ContainsKey(origin_to_contexts_, origin));
151
152  DVLOG(1) << "Starting ProcessLocalChange";
153
154  local_sync_callback_ = callback;
155
156  sync_context_->GetFileForLocalSync(
157      origin_to_contexts_[origin],
158      base::Bind(&LocalFileSyncService::DidGetFileForLocalSync,
159                 AsWeakPtr()));
160}
161
162void LocalFileSyncService::SetLocalChangeProcessor(
163    LocalChangeProcessor* processor) {
164  local_change_processor_ = processor;
165}
166
167void LocalFileSyncService::HasPendingLocalChanges(
168    const FileSystemURL& url,
169    const HasPendingLocalChangeCallback& callback) {
170  if (!ContainsKey(origin_to_contexts_, url.origin())) {
171    base::MessageLoopProxy::current()->PostTask(
172        FROM_HERE,
173        base::Bind(callback, SYNC_FILE_ERROR_INVALID_URL, false));
174    return;
175  }
176  sync_context_->HasPendingLocalChanges(
177      origin_to_contexts_[url.origin()], url, callback);
178}
179
180void LocalFileSyncService::ClearSyncFlagForURL(
181    const FileSystemURL& url) {
182  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
183  sync_context_->ClearSyncFlagForURL(url);
184}
185
186void LocalFileSyncService::GetLocalFileMetadata(
187    const FileSystemURL& url, const SyncFileMetadataCallback& callback) {
188  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
189  sync_context_->GetFileMetadata(origin_to_contexts_[url.origin()],
190                                 url, callback);
191}
192
193void LocalFileSyncService::PrepareForProcessRemoteChange(
194    const FileSystemURL& url,
195    const PrepareChangeCallback& callback) {
196  DVLOG(1) << "PrepareForProcessRemoteChange: " << url.DebugString();
197
198  if (!ContainsKey(origin_to_contexts_, url.origin())) {
199    // This could happen if a remote sync is triggered for the app that hasn't
200    // been initialized in this service.
201    DCHECK(profile_);
202    // The given url.origin() must be for valid installed app.
203    ExtensionService* extension_service =
204        extensions::ExtensionSystem::Get(profile_)->extension_service();
205    const extensions::Extension* extension = extension_service->GetInstalledApp(
206        url.origin());
207    if (!extension) {
208      util::Log(
209          logging::LOG_WARNING,
210          FROM_HERE,
211          "PrepareForProcessRemoteChange called for non-existing origin: %s",
212          url.origin().spec().c_str());
213
214      // The extension has been uninstalled and this method is called
215      // before the remote changes for the origin are removed.
216      callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC,
217                   SyncFileMetadata(), FileChangeList());
218      return;
219    }
220    GURL site_url = extension_service->GetSiteForExtensionId(extension->id());
221    DCHECK(!site_url.is_empty());
222    scoped_refptr<fileapi::FileSystemContext> file_system_context =
223        content::BrowserContext::GetStoragePartitionForSite(
224            profile_, site_url)->GetFileSystemContext();
225    MaybeInitializeFileSystemContext(
226        url.origin(),
227        file_system_context.get(),
228        base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync,
229                   AsWeakPtr(),
230                   url,
231                   file_system_context,
232                   callback));
233    return;
234  }
235
236  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
237  sync_context_->PrepareForSync(
238      origin_to_contexts_[url.origin()], url,
239      base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter, callback));
240}
241
242void LocalFileSyncService::ApplyRemoteChange(
243    const FileChange& change,
244    const base::FilePath& local_path,
245    const FileSystemURL& url,
246    const SyncStatusCallback& callback) {
247  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
248  sync_context_->ApplyRemoteChange(
249      origin_to_contexts_[url.origin()],
250      change, local_path, url, callback);
251}
252
253void LocalFileSyncService::ClearLocalChanges(
254    const FileSystemURL& url,
255    const base::Closure& completion_callback) {
256  DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
257  sync_context_->ClearChangesForURL(origin_to_contexts_[url.origin()],
258                                    url, 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  DCHECK(!local_sync_callback_.is_null());
366  if (status != SYNC_STATUS_OK) {
367    RunLocalSyncCallback(status, sync_file_info.url);
368    return;
369  }
370  if (sync_file_info.changes.empty()) {
371    // There's a slight chance this could happen.
372    SyncFileCallback callback = local_sync_callback_;
373    local_sync_callback_.Reset();
374    ProcessLocalChange(callback);
375    return;
376  }
377
378  DVLOG(1) << "ProcessLocalChange: " << sync_file_info.url.DebugString()
379           << " change:" << sync_file_info.changes.front().DebugString();
380
381  local_change_processor_->ApplyLocalChange(
382      sync_file_info.changes.front(),
383      sync_file_info.local_file_path,
384      sync_file_info.metadata,
385      sync_file_info.url,
386      base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
387                 AsWeakPtr(),
388                 sync_file_info,
389                 sync_file_info.changes.front(),
390                 sync_file_info.changes.PopAndGetNewList()));
391}
392
393void LocalFileSyncService::ProcessNextChangeForURL(
394    const LocalFileSyncInfo& sync_file_info,
395    const FileChange& last_change,
396    const FileChangeList& changes,
397    SyncStatusCode status) {
398  DVLOG(1) << "Processed one local change: "
399           << sync_file_info.url.DebugString()
400           << " change:" << last_change.DebugString()
401           << " status:" << status;
402
403  if (status == SYNC_FILE_ERROR_NOT_FOUND &&
404      last_change.change() == FileChange::FILE_CHANGE_DELETE) {
405    // This must be ok (and could happen).
406    status = SYNC_STATUS_OK;
407  }
408
409  // TODO(kinuko,tzik): Handle other errors that should not be considered
410  // a sync error.
411
412  const FileSystemURL& url = sync_file_info.url;
413  if (status != SYNC_STATUS_OK || changes.empty()) {
414    if (status == SYNC_STATUS_OK || status == SYNC_STATUS_HAS_CONFLICT) {
415      // Clear the recorded changes for the URL if the sync was successfull
416      // OR has failed due to conflict (so that we won't stick to the same
417      // conflicting file again and again).
418      DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
419      sync_context_->ClearChangesForURL(
420          origin_to_contexts_[url.origin()], url,
421          base::Bind(&LocalFileSyncService::RunLocalSyncCallback,
422                     AsWeakPtr(), status, url));
423      return;
424    }
425    RunLocalSyncCallback(status, url);
426    return;
427  }
428
429  local_change_processor_->ApplyLocalChange(
430      changes.front(),
431      sync_file_info.local_file_path,
432      sync_file_info.metadata,
433      url,
434      base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
435                 AsWeakPtr(), sync_file_info,
436                 changes.front(), changes.PopAndGetNewList()));
437}
438
439}  // namespace sync_file_system
440