sync_file_system_service.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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/sync_file_system_service.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/logging.h"
11#include "base/memory/ref_counted.h"
12#include "base/stl_util.h"
13#include "chrome/browser/extensions/api/sync_file_system/extension_sync_event_observer.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/profiles/profile_dependency_manager.h"
16#include "chrome/browser/sync/profile_sync_service.h"
17#include "chrome/browser/sync/profile_sync_service_factory.h"
18#include "chrome/browser/sync_file_system/drive_file_sync_service.h"
19#include "chrome/browser/sync_file_system/local_file_sync_service.h"
20#include "chrome/browser/sync_file_system/sync_event_observer.h"
21#include "chrome/common/chrome_notification_types.h"
22#include "chrome/common/extensions/extension.h"
23#include "content/public/browser/browser_thread.h"
24#include "content/public/browser/notification_details.h"
25#include "content/public/browser/notification_service.h"
26#include "googleurl/src/gurl.h"
27#include "webkit/fileapi/file_system_context.h"
28#include "webkit/fileapi/syncable/sync_direction.h"
29#include "webkit/fileapi/syncable/sync_file_metadata.h"
30#include "webkit/fileapi/syncable/sync_status_code.h"
31
32using content::BrowserThread;
33using fileapi::FileSystemURL;
34using fileapi::FileSystemURLSet;
35
36namespace sync_file_system {
37
38namespace {
39
40SyncEventObserver::SyncServiceState RemoteStateToSyncServiceState(
41    RemoteServiceState state) {
42  switch (state) {
43    case REMOTE_SERVICE_OK:
44      return SyncEventObserver::SYNC_SERVICE_RUNNING;
45    case REMOTE_SERVICE_TEMPORARY_UNAVAILABLE:
46      return SyncEventObserver::SYNC_SERVICE_TEMPORARY_UNAVAILABLE;
47    case REMOTE_SERVICE_AUTHENTICATION_REQUIRED:
48      return SyncEventObserver::SYNC_SERVICE_AUTHENTICATION_REQUIRED;
49    case REMOTE_SERVICE_DISABLED:
50      return SyncEventObserver::SYNC_SERVICE_DISABLED;
51  }
52  NOTREACHED();
53  return SyncEventObserver::SYNC_SERVICE_DISABLED;
54}
55
56void DidHandleOriginForExtensionUnloadedEvent(
57    int type,
58    extension_misc::UnloadedExtensionReason reason,
59    const GURL& origin,
60    SyncStatusCode code) {
61  DCHECK(chrome::NOTIFICATION_EXTENSION_UNLOADED == type);
62  DCHECK(extension_misc::UNLOAD_REASON_DISABLE == reason ||
63         extension_misc::UNLOAD_REASON_UNINSTALL == reason);
64  if (code != SYNC_STATUS_OK) {
65    switch (reason) {
66      case extension_misc::UNLOAD_REASON_DISABLE:
67        LOG(WARNING) << "Disabling origin for UNLOAD(DISABLE) failed: "
68                     << origin.spec();
69        break;
70      case extension_misc::UNLOAD_REASON_UNINSTALL:
71        LOG(WARNING) << "Uninstall origin for UNLOAD(UNINSTALL) failed: "
72                     << origin.spec();
73        break;
74      default:
75        break;
76    }
77  }
78}
79
80void DidHandleOriginForExtensionEnabledEvent(
81    int type,
82    const GURL& origin,
83    SyncStatusCode code) {
84  DCHECK(chrome::NOTIFICATION_EXTENSION_ENABLED == type);
85  if (code != SYNC_STATUS_OK)
86    LOG(WARNING) << "Enabling origin for ENABLED failed: " << origin.spec();
87}
88
89}  // namespace
90
91void SyncFileSystemService::Shutdown() {
92  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
93
94  local_file_service_->Shutdown();
95  local_file_service_.reset();
96
97  remote_file_service_.reset();
98
99  ProfileSyncServiceBase* profile_sync_service =
100      ProfileSyncServiceFactory::GetForProfile(profile_);
101  if (profile_sync_service)
102    profile_sync_service->RemoveObserver(this);
103
104  profile_ = NULL;
105}
106
107SyncFileSystemService::~SyncFileSystemService() {
108  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
109  DCHECK(!profile_);
110}
111
112void SyncFileSystemService::InitializeForApp(
113    fileapi::FileSystemContext* file_system_context,
114    const std::string& service_name,
115    const GURL& app_origin,
116    const SyncStatusCallback& callback) {
117  DCHECK(local_file_service_);
118  DCHECK(remote_file_service_);
119  DCHECK(app_origin == app_origin.GetOrigin());
120
121  DVLOG(1) << "InitializeForApp: " << app_origin.spec();
122
123  local_file_service_->MaybeInitializeFileSystemContext(
124      app_origin, service_name, file_system_context,
125      base::Bind(&SyncFileSystemService::DidInitializeFileSystem,
126                 AsWeakPtr(), app_origin, callback));
127}
128
129void SyncFileSystemService::GetFileSyncStatus(
130    const FileSystemURL& url, const SyncFileStatusCallback& callback) {
131  DCHECK(local_file_service_);
132  DCHECK(remote_file_service_);
133
134  // It's possible to get an invalid FileEntry.
135  if (!url.is_valid()) {
136    base::MessageLoopProxy::current()->PostTask(
137        FROM_HERE,
138        base::Bind(callback,
139                   SYNC_FILE_ERROR_INVALID_URL,
140                   SYNC_FILE_STATUS_UNKNOWN));
141    return;
142  }
143
144  if (remote_file_service_->IsConflicting(url)) {
145    base::MessageLoopProxy::current()->PostTask(
146        FROM_HERE,
147        base::Bind(callback,
148                   SYNC_STATUS_OK,
149                   SYNC_FILE_STATUS_CONFLICTING));
150    return;
151  }
152
153  local_file_service_->HasPendingLocalChanges(
154      url,
155      base::Bind(&SyncFileSystemService::DidGetLocalChangeStatus,
156                 AsWeakPtr(), callback));
157}
158
159void SyncFileSystemService::AddSyncEventObserver(SyncEventObserver* observer) {
160  observers_.AddObserver(observer);
161}
162
163void SyncFileSystemService::RemoveSyncEventObserver(
164    SyncEventObserver* observer) {
165  observers_.RemoveObserver(observer);
166}
167
168ConflictResolutionPolicy
169SyncFileSystemService::GetConflictResolutionPolicy() const {
170  return remote_file_service_->GetConflictResolutionPolicy();
171}
172
173SyncStatusCode SyncFileSystemService::SetConflictResolutionPolicy(
174    ConflictResolutionPolicy policy) {
175  return remote_file_service_->SetConflictResolutionPolicy(policy);
176}
177
178SyncFileSystemService::SyncFileSystemService(Profile* profile)
179    : profile_(profile),
180      pending_local_changes_(0),
181      pending_remote_changes_(0),
182      local_sync_running_(false),
183      remote_sync_running_(false),
184      is_waiting_remote_sync_enabled_(false),
185      sync_enabled_(true) {
186}
187
188void SyncFileSystemService::Initialize(
189    scoped_ptr<LocalFileSyncService> local_file_service,
190    scoped_ptr<RemoteFileSyncService> remote_file_service) {
191  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192  DCHECK(local_file_service);
193  DCHECK(remote_file_service);
194  DCHECK(profile_);
195
196  local_file_service_ = local_file_service.Pass();
197  remote_file_service_ = remote_file_service.Pass();
198
199  local_file_service_->AddChangeObserver(this);
200  local_file_service_->SetLocalChangeProcessor(
201      remote_file_service_->GetLocalChangeProcessor());
202
203  remote_file_service_->AddServiceObserver(this);
204  remote_file_service_->AddFileStatusObserver(this);
205  remote_file_service_->SetRemoteChangeProcessor(local_file_service_.get());
206
207  ProfileSyncServiceBase* profile_sync_service =
208      ProfileSyncServiceFactory::GetForProfile(profile_);
209  if (profile_sync_service) {
210    UpdateSyncEnabledStatus(profile_sync_service);
211    profile_sync_service->AddObserver(this);
212  }
213
214  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
215                 content::Source<Profile>(profile_));
216  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_ENABLED,
217                 content::Source<Profile>(profile_));
218}
219
220void SyncFileSystemService::DidInitializeFileSystem(
221    const GURL& app_origin,
222    const SyncStatusCallback& callback,
223    SyncStatusCode status) {
224  DVLOG(1) << "DidInitializeFileSystem: "
225           << app_origin.spec() << " " << status;
226
227  if (status != SYNC_STATUS_OK) {
228    callback.Run(status);
229    return;
230  }
231
232  // Local side of initialization for the app is done.
233  // Continue on initializing the remote side.
234  remote_file_service_->RegisterOriginForTrackingChanges(
235      app_origin,
236      base::Bind(&SyncFileSystemService::DidRegisterOrigin,
237                 AsWeakPtr(), app_origin, callback));
238}
239
240void SyncFileSystemService::DidRegisterOrigin(
241    const GURL& app_origin,
242    const SyncStatusCallback& callback,
243    SyncStatusCode status) {
244  DVLOG(1) << "DidRegisterOrigin: " << app_origin.spec() << " " << status;
245
246  callback.Run(status);
247}
248
249void SyncFileSystemService::SetSyncEnabledForTesting(bool enabled) {
250  sync_enabled_ = enabled;
251  remote_file_service_->SetSyncEnabled(sync_enabled_);
252}
253
254void SyncFileSystemService::MaybeStartSync() {
255  if (!profile_ || !sync_enabled_)
256    return;
257
258  DCHECK(local_file_service_);
259  DCHECK(remote_file_service_);
260
261  MaybeStartRemoteSync();
262  MaybeStartLocalSync();
263}
264
265void SyncFileSystemService::MaybeStartRemoteSync() {
266  if (remote_file_service_->GetCurrentState() == REMOTE_SERVICE_DISABLED)
267    return;
268  // See if we cannot / should not start a new remote sync.
269  if (remote_sync_running_ || pending_remote_changes_ == 0)
270    return;
271  // If we have registered a URL for waiting until sync is enabled on a
272  // file (and the registerred URL seems to be still valid) it won't be
273  // worth trying to start another remote sync.
274  if (is_waiting_remote_sync_enabled_)
275    return;
276  DCHECK(sync_enabled_);
277  DVLOG(1) << "Calling ProcessRemoteChange";
278  remote_sync_running_ = true;
279  remote_file_service_->ProcessRemoteChange(
280      base::Bind(&SyncFileSystemService::DidProcessRemoteChange,
281                 AsWeakPtr()));
282}
283
284void SyncFileSystemService::MaybeStartLocalSync() {
285  // If the remote service is not ready probably we should not start a
286  // local sync yet.
287  // (We should be still trying a remote sync so the state should become OK
288  // if the remote-side attempt succeeds.)
289  if (remote_file_service_->GetCurrentState() != REMOTE_SERVICE_OK)
290    return;
291  // See if we cannot / should not start a new local sync.
292  if (local_sync_running_ || pending_local_changes_ == 0)
293    return;
294  DVLOG(1) << "Calling ProcessLocalChange";
295  local_sync_running_ = true;
296  local_file_service_->ProcessLocalChange(
297      base::Bind(&SyncFileSystemService::DidProcessLocalChange,
298                 AsWeakPtr()));
299}
300
301void SyncFileSystemService::DidProcessRemoteChange(
302    SyncStatusCode status,
303    const FileSystemURL& url) {
304  DVLOG(1) << "DidProcessRemoteChange: "
305           << " status=" << status
306           << " (" << SyncStatusCodeToString(status) << ")"
307           << " url=" << url.DebugString();
308  DCHECK(remote_sync_running_);
309  remote_sync_running_ = false;
310
311  if (status != SYNC_STATUS_NO_CHANGE_TO_SYNC &&
312      remote_file_service_->GetCurrentState() != REMOTE_SERVICE_DISABLED) {
313    DCHECK(url.is_valid());
314    local_file_service_->ClearSyncFlagForURL(url);
315  }
316
317  if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC) {
318    // We seem to have no changes to work on for now.
319    // TODO(kinuko): Might be better setting a timer to call MaybeStartSync.
320    return;
321  }
322  if (status == SYNC_STATUS_FILE_BUSY) {
323    is_waiting_remote_sync_enabled_ = true;
324    local_file_service_->RegisterURLForWaitingSync(
325        url, base::Bind(&SyncFileSystemService::OnSyncEnabledForRemoteSync,
326                        AsWeakPtr()));
327    return;
328  }
329
330  base::MessageLoopProxy::current()->PostTask(
331      FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync,
332                            AsWeakPtr()));
333}
334
335void SyncFileSystemService::DidProcessLocalChange(
336    SyncStatusCode status, const FileSystemURL& url) {
337  DVLOG(1) << "DidProcessLocalChange:"
338           << " status=" << status
339           << " (" << SyncStatusCodeToString(status) << ")"
340           << " url=" << url.DebugString();
341  DCHECK(local_sync_running_);
342  local_sync_running_ = false;
343
344  if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC) {
345    // We seem to have no changes to work on for now.
346    return;
347  }
348
349  DCHECK(url.is_valid());
350  local_file_service_->ClearSyncFlagForURL(url);
351
352  base::MessageLoopProxy::current()->PostTask(
353      FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync,
354                            AsWeakPtr()));
355}
356
357void SyncFileSystemService::DidGetLocalChangeStatus(
358    const SyncFileStatusCallback& callback,
359    SyncStatusCode status,
360    bool has_pending_local_changes) {
361  callback.Run(
362      status,
363      has_pending_local_changes ?
364          SYNC_FILE_STATUS_HAS_PENDING_CHANGES : SYNC_FILE_STATUS_SYNCED);
365}
366
367void SyncFileSystemService::OnSyncEnabledForRemoteSync() {
368  is_waiting_remote_sync_enabled_ = false;
369  MaybeStartRemoteSync();
370}
371
372void SyncFileSystemService::OnLocalChangeAvailable(int64 pending_changes) {
373  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
374  DCHECK_GE(pending_changes, 0);
375  DVLOG(1) << "OnLocalChangeAvailable: " << pending_changes;
376  pending_local_changes_ = pending_changes;
377  base::MessageLoopProxy::current()->PostTask(
378      FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync,
379                            AsWeakPtr()));
380}
381
382void SyncFileSystemService::OnRemoteChangeQueueUpdated(int64 pending_changes) {
383  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
384  DCHECK_GE(pending_changes, 0);
385  DVLOG(1) << "OnRemoteChangeQueueUpdated: " << pending_changes;
386  pending_remote_changes_ = pending_changes;
387  if (pending_changes > 0) {
388    // The smallest change available might have changed from the previous one.
389    // Reset the is_waiting_remote_sync_enabled_ flag so that we can retry.
390    is_waiting_remote_sync_enabled_ = false;
391  }
392  base::MessageLoopProxy::current()->PostTask(
393      FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync,
394                            AsWeakPtr()));
395}
396
397void SyncFileSystemService::OnRemoteServiceStateUpdated(
398    RemoteServiceState state,
399    const std::string& description) {
400  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
401  DVLOG(1) << "OnRemoteServiceStateUpdated: " << state
402           << " " << description;
403  if (state == REMOTE_SERVICE_OK) {
404    base::MessageLoopProxy::current()->PostTask(
405        FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync,
406                              AsWeakPtr()));
407  }
408
409  FOR_EACH_OBSERVER(
410      SyncEventObserver, observers_,
411      OnSyncStateUpdated(GURL(),
412                         RemoteStateToSyncServiceState(state),
413                         description));
414}
415
416void SyncFileSystemService::Observe(
417    int type,
418    const content::NotificationSource& source,
419    const content::NotificationDetails& details) {
420  switch (type) {
421    // Delivered when an app is disabled, reloaded or restarted.
422    case chrome::NOTIFICATION_EXTENSION_UNLOADED:
423      HandleExtensionUnloaded(type, details);
424      break;
425    // Delivered when an app is enabled, reloaded or restarted.
426    case chrome::NOTIFICATION_EXTENSION_ENABLED:
427      HandleExtensionEnabled(type, details);
428      break;
429    default:
430      NOTREACHED() << "Unknown notification.";
431      break;
432  }
433}
434
435void SyncFileSystemService::HandleExtensionUnloaded(
436    int type,
437    const content::NotificationDetails& details) {
438  content::Details<const extensions::UnloadedExtensionInfo> info =
439      content::Details<const extensions::UnloadedExtensionInfo>(details);
440  std::string extension_id = info->extension->id();
441  GURL app_origin =
442      extensions::Extension::GetBaseURLFromExtensionId(extension_id);
443
444  switch (info->reason) {
445    case extension_misc::UNLOAD_REASON_DISABLE:
446      DVLOG(1) << "Handle extension notification for UNLOAD(DISABLE): "
447               << app_origin;
448      remote_file_service_->DisableOriginForTrackingChanges(
449          app_origin,
450          base::Bind(&DidHandleOriginForExtensionUnloadedEvent,
451                     type, info->reason, app_origin));
452      local_file_service_->SetOriginEnabled(app_origin, false);
453      break;
454    case extension_misc::UNLOAD_REASON_UNINSTALL:
455      DVLOG(1) << "Handle extension notification for UNLOAD(UNINSTALL): "
456               << app_origin;
457      remote_file_service_->UninstallOrigin(
458          app_origin,
459          base::Bind(&DidHandleOriginForExtensionUnloadedEvent,
460                     type, info->reason, app_origin));
461      local_file_service_->SetOriginEnabled(app_origin, false);
462      break;
463    default:
464      // Nothing to do.
465      break;
466  }
467}
468
469void SyncFileSystemService::HandleExtensionEnabled(
470    int type,
471    const content::NotificationDetails& details) {
472  std::string extension_id =
473      content::Details<const extensions::Extension>(details)->id();
474  GURL app_origin =
475      extensions::Extension::GetBaseURLFromExtensionId(extension_id);
476  DVLOG(1) << "Handle extension notification for ENABLED: " << app_origin;
477  remote_file_service_->EnableOriginForTrackingChanges(
478      app_origin,
479      base::Bind(&DidHandleOriginForExtensionEnabledEvent, type, app_origin));
480  local_file_service_->SetOriginEnabled(app_origin, true);
481}
482
483void SyncFileSystemService::OnStateChanged() {
484  ProfileSyncServiceBase* profile_sync_service =
485      ProfileSyncServiceFactory::GetForProfile(profile_);
486  if (profile_sync_service)
487    UpdateSyncEnabledStatus(profile_sync_service);
488}
489
490void SyncFileSystemService::OnFileStatusChanged(
491    const FileSystemURL& url,
492    SyncFileStatus sync_status,
493    SyncAction action_taken,
494    SyncDirection direction) {
495  FOR_EACH_OBSERVER(
496      SyncEventObserver, observers_,
497      OnFileSynced(url, sync_status, action_taken, direction));
498}
499
500void SyncFileSystemService::UpdateSyncEnabledStatus(
501    ProfileSyncServiceBase* profile_sync_service) {
502  if (!profile_sync_service->HasSyncSetupCompleted())
503    return;
504  sync_enabled_ = profile_sync_service->GetPreferredDataTypes().Has(
505      syncer::APPS);
506  remote_file_service_->SetSyncEnabled(sync_enabled_);
507  if (sync_enabled_) {
508    base::MessageLoopProxy::current()->PostTask(
509        FROM_HERE, base::Bind(&SyncFileSystemService::MaybeStartSync,
510                              AsWeakPtr()));
511  }
512}
513
514}  // namespace sync_file_system
515