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