1// Copyright (c) 2011 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/glue/theme_change_processor.h"
6
7#include "base/logging.h"
8#include "chrome/browser/profiles/profile.h"
9#include "chrome/browser/sync/engine/syncapi.h"
10#include "chrome/browser/sync/glue/theme_util.h"
11#include "chrome/browser/sync/protocol/theme_specifics.pb.h"
12#include "chrome/browser/themes/theme_service.h"
13#include "chrome/browser/themes/theme_service_factory.h"
14#include "chrome/common/extensions/extension.h"
15#include "content/common/notification_details.h"
16#include "content/common/notification_source.h"
17
18namespace browser_sync {
19
20ThemeChangeProcessor::ThemeChangeProcessor(
21    UnrecoverableErrorHandler* error_handler)
22    : ChangeProcessor(error_handler),
23      profile_(NULL) {
24  DCHECK(error_handler);
25}
26
27ThemeChangeProcessor::~ThemeChangeProcessor() {}
28
29void ThemeChangeProcessor::Observe(NotificationType type,
30                                   const NotificationSource& source,
31                                   const NotificationDetails& details) {
32  DCHECK(running());
33  DCHECK(profile_);
34  DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
35
36  sync_api::WriteTransaction trans(share_handle());
37  sync_api::WriteNode node(&trans);
38  if (!node.InitByClientTagLookup(syncable::THEMES,
39                                  kCurrentThemeClientTag)) {
40    std::string err = "Could not create node with client tag: ";
41    error_handler()->OnUnrecoverableError(FROM_HERE,
42                                          err + kCurrentThemeClientTag);
43    return;
44  }
45
46  sync_pb::ThemeSpecifics old_theme_specifics = node.GetThemeSpecifics();
47  // Make sure to base new_theme_specifics on old_theme_specifics so
48  // we preserve the state of use_system_theme_by_default.
49  sync_pb::ThemeSpecifics new_theme_specifics = old_theme_specifics;
50  GetThemeSpecificsFromCurrentTheme(profile_, &new_theme_specifics);
51  // Do a write only if something actually changed so as to guard
52  // against cycles.
53  if (!AreThemeSpecificsEqual(old_theme_specifics, new_theme_specifics)) {
54    node.SetThemeSpecifics(new_theme_specifics);
55  }
56  return;
57}
58
59void ThemeChangeProcessor::ApplyChangesFromSyncModel(
60    const sync_api::BaseTransaction* trans,
61    const sync_api::SyncManager::ChangeRecord* changes,
62    int change_count) {
63  if (!running()) {
64    return;
65  }
66  // TODO(akalin): Normally, we should only have a single change and
67  // it should be an update.  However, the syncapi may occasionally
68  // generates multiple changes.  When we fix syncapi to not do that,
69  // we can remove the extra logic below.  See:
70  // http://code.google.com/p/chromium/issues/detail?id=41696 .
71  if (change_count < 1) {
72    std::string err("Unexpected change_count: ");
73    err += change_count;
74    error_handler()->OnUnrecoverableError(FROM_HERE, err);
75    return;
76  }
77  if (change_count > 1) {
78    LOG(WARNING) << change_count << " theme changes detected; "
79                 << "only applying the last one";
80  }
81  const sync_api::SyncManager::ChangeRecord& change =
82      changes[change_count - 1];
83  if (change.action != sync_api::SyncManager::ChangeRecord::ACTION_UPDATE &&
84      change.action != sync_api::SyncManager::ChangeRecord::ACTION_DELETE) {
85    std::string err = "strange theme change.action " + change.action;
86    error_handler()->OnUnrecoverableError(FROM_HERE, err);
87    return;
88  }
89  sync_pb::ThemeSpecifics theme_specifics;
90  // If the action is a delete, simply use the default values for
91  // ThemeSpecifics, which would cause the default theme to be set.
92  if (change.action != sync_api::SyncManager::ChangeRecord::ACTION_DELETE) {
93    sync_api::ReadNode node(trans);
94    if (!node.InitByIdLookup(change.id)) {
95      error_handler()->OnUnrecoverableError(FROM_HERE,
96                                            "Theme node lookup failed.");
97      return;
98    }
99    DCHECK_EQ(node.GetModelType(), syncable::THEMES);
100    DCHECK(profile_);
101    theme_specifics = node.GetThemeSpecifics();
102  }
103  StopObserving();
104  SetCurrentThemeFromThemeSpecificsIfNecessary(theme_specifics, profile_);
105  StartObserving();
106}
107
108void ThemeChangeProcessor::StartImpl(Profile* profile) {
109  DCHECK(profile);
110  profile_ = profile;
111  StartObserving();
112}
113
114void ThemeChangeProcessor::StopImpl() {
115  StopObserving();
116  profile_ = NULL;
117}
118
119void ThemeChangeProcessor::StartObserving() {
120  DCHECK(profile_);
121  VLOG(1) << "Observing BROWSER_THEME_CHANGED";
122  notification_registrar_.Add(
123      this, NotificationType::BROWSER_THEME_CHANGED,
124      Source<ThemeService>(
125          ThemeServiceFactory::GetForProfile(profile_)));
126}
127
128void ThemeChangeProcessor::StopObserving() {
129  DCHECK(profile_);
130  VLOG(1) << "Unobserving all notifications";
131  notification_registrar_.RemoveAll();
132}
133
134}  // namespace browser_sync
135