blob: 3389443e055fc321051edd8da9810b22d886243e [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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/themes/theme_syncable_service.h"
6
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01007#include "base/strings/stringprintf.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +00008#include "chrome/browser/extensions/extension_service.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00009#include "chrome/browser/extensions/extension_system.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000010#include "chrome/browser/profiles/profile.h"
11#include "chrome/browser/themes/theme_service.h"
12#include "chrome/common/extensions/extension.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000013#include "chrome/common/extensions/manifest_url_handler.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010014#include "chrome/common/extensions/sync_helper.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000015#include "sync/protocol/sync.pb.h"
16#include "sync/protocol/theme_specifics.pb.h"
17
18using std::string;
19
20namespace {
21
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010022bool IsTheme(const extensions::Extension* extension) {
23 return extension->is_theme();
Torne (Richard Coles)58218062012-11-14 11:43:16 +000024}
25
26// TODO(akalin): Remove this.
27bool IsSystemThemeDistinctFromDefaultTheme() {
28#if defined(TOOLKIT_GTK)
29 return true;
30#else
31 return false;
32#endif
33}
34
35} // namespace
36
37const char ThemeSyncableService::kCurrentThemeClientTag[] = "current_theme";
38const char ThemeSyncableService::kCurrentThemeNodeTitle[] = "Current Theme";
39
40ThemeSyncableService::ThemeSyncableService(Profile* profile,
41 ThemeService* theme_service)
42 : profile_(profile),
43 theme_service_(theme_service),
44 use_system_theme_by_default_(false) {
45 DCHECK(profile_);
46 DCHECK(theme_service_);
47}
48
49ThemeSyncableService::~ThemeSyncableService() {
50}
51
52void ThemeSyncableService::OnThemeChange() {
53 if (sync_processor_.get()) {
54 sync_pb::ThemeSpecifics current_specifics;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000055 if (!GetThemeSpecificsFromCurrentTheme(&current_specifics))
56 return; // Current theme is unsyncable.
Torne (Richard Coles)58218062012-11-14 11:43:16 +000057 ProcessNewTheme(syncer::SyncChange::ACTION_UPDATE, current_specifics);
58 use_system_theme_by_default_ =
59 current_specifics.use_system_theme_by_default();
60 }
61}
62
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000063syncer::SyncMergeResult ThemeSyncableService::MergeDataAndStartSyncing(
Torne (Richard Coles)58218062012-11-14 11:43:16 +000064 syncer::ModelType type,
65 const syncer::SyncDataList& initial_sync_data,
66 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
67 scoped_ptr<syncer::SyncErrorFactory> error_handler) {
68 DCHECK(thread_checker_.CalledOnValidThread());
69 DCHECK(!sync_processor_.get());
70 DCHECK(sync_processor.get());
71 DCHECK(error_handler.get());
72
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000073 syncer::SyncMergeResult merge_result(type);
Torne (Richard Coles)58218062012-11-14 11:43:16 +000074 sync_processor_ = sync_processor.Pass();
75 sync_error_handler_ = error_handler.Pass();
76
77 if (initial_sync_data.size() > 1) {
78 sync_error_handler_->CreateAndUploadError(
79 FROM_HERE,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000080 base::StringPrintf("Received %d theme specifics.",
81 static_cast<int>(initial_sync_data.size())));
Torne (Richard Coles)58218062012-11-14 11:43:16 +000082 }
83
84 sync_pb::ThemeSpecifics current_specifics;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000085 if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
86 // Current theme is unsyncable - don't overwrite from sync data, and don't
87 // save the unsyncable theme to sync data.
88 return merge_result;
89 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +000090
91 // Find the last SyncData that has theme data and set the current theme from
92 // it.
93 for (syncer::SyncDataList::const_reverse_iterator sync_data =
94 initial_sync_data.rbegin(); sync_data != initial_sync_data.rend();
95 ++sync_data) {
96 if (sync_data->GetSpecifics().has_theme()) {
97 MaybeSetTheme(current_specifics, *sync_data);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000098 return merge_result;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000099 }
100 }
101
102 // No theme specifics are found. Create one according to current theme.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000103 merge_result.set_error(ProcessNewTheme(
104 syncer::SyncChange::ACTION_ADD, current_specifics));
105 return merge_result;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000106}
107
108void ThemeSyncableService::StopSyncing(syncer::ModelType type) {
109 DCHECK(thread_checker_.CalledOnValidThread());
110 DCHECK_EQ(type, syncer::THEMES);
111
112 sync_processor_.reset();
113 sync_error_handler_.reset();
114}
115
116syncer::SyncDataList ThemeSyncableService::GetAllSyncData(
117 syncer::ModelType type) const {
118 DCHECK(thread_checker_.CalledOnValidThread());
119 DCHECK_EQ(type, syncer::THEMES);
120
121 syncer::SyncDataList list;
122 sync_pb::EntitySpecifics entity_specifics;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000123 if (GetThemeSpecificsFromCurrentTheme(entity_specifics.mutable_theme())) {
124 list.push_back(syncer::SyncData::CreateLocalData(kCurrentThemeClientTag,
125 kCurrentThemeNodeTitle,
126 entity_specifics));
127 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000128 return list;
129}
130
131syncer::SyncError ThemeSyncableService::ProcessSyncChanges(
132 const tracked_objects::Location& from_here,
133 const syncer::SyncChangeList& change_list) {
134 DCHECK(thread_checker_.CalledOnValidThread());
135
136 if (!sync_processor_.get()) {
137 return syncer::SyncError(FROM_HERE,
Ben Murdocheb525c52013-07-10 11:40:50 +0100138 syncer::SyncError::DATATYPE_ERROR,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000139 "Theme syncable service is not started.",
140 syncer::THEMES);
141 }
142
143 // TODO(akalin): Normally, we should only have a single change and
144 // it should be an update. However, the syncapi may occasionally
145 // generates multiple changes. When we fix syncapi to not do that,
146 // we can remove the extra logic below. See:
147 // http://code.google.com/p/chromium/issues/detail?id=41696 .
148 if (change_list.size() != 1) {
149 string err_msg = base::StringPrintf("Received %d theme changes: ",
150 static_cast<int>(change_list.size()));
151 for (size_t i = 0; i < change_list.size(); ++i) {
152 base::StringAppendF(&err_msg, "[%s] ", change_list[i].ToString().c_str());
153 }
154 sync_error_handler_->CreateAndUploadError(FROM_HERE, err_msg);
155 } else if (change_list.begin()->change_type() !=
156 syncer::SyncChange::ACTION_ADD
157 && change_list.begin()->change_type() !=
158 syncer::SyncChange::ACTION_UPDATE) {
159 sync_error_handler_->CreateAndUploadError(
160 FROM_HERE,
161 "Invalid theme change: " + change_list.begin()->ToString());
162 }
163
164 sync_pb::ThemeSpecifics current_specifics;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000165 if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
166 // Current theme is unsyncable, so don't overwrite it.
167 return syncer::SyncError();
168 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000169
170 // Set current theme from the theme specifics of the last change of type
171 // |ACTION_ADD| or |ACTION_UPDATE|.
172 for (syncer::SyncChangeList::const_reverse_iterator theme_change =
173 change_list.rbegin(); theme_change != change_list.rend();
174 ++theme_change) {
175 if (theme_change->sync_data().GetSpecifics().has_theme() &&
176 (theme_change->change_type() == syncer::SyncChange::ACTION_ADD ||
177 theme_change->change_type() == syncer::SyncChange::ACTION_UPDATE)) {
178 MaybeSetTheme(current_specifics, theme_change->sync_data());
179 return syncer::SyncError();
180 }
181 }
182
183 return syncer::SyncError(FROM_HERE,
Ben Murdocheb525c52013-07-10 11:40:50 +0100184 syncer::SyncError::DATATYPE_ERROR,
185 "Didn't find valid theme specifics",
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000186 syncer::THEMES);
187}
188
189void ThemeSyncableService::MaybeSetTheme(
190 const sync_pb::ThemeSpecifics& current_specs,
191 const syncer::SyncData& sync_data) {
192 const sync_pb::ThemeSpecifics& sync_theme = sync_data.GetSpecifics().theme();
193 use_system_theme_by_default_ = sync_theme.use_system_theme_by_default();
194 DVLOG(1) << "Set current theme from specifics: " << sync_data.ToString();
195 if (!AreThemeSpecificsEqual(current_specs, sync_theme,
196 IsSystemThemeDistinctFromDefaultTheme())) {
197 SetCurrentThemeFromThemeSpecifics(sync_theme);
198 } else {
199 DVLOG(1) << "Skip setting theme because specs are equal";
200 }
201}
202
203void ThemeSyncableService::SetCurrentThemeFromThemeSpecifics(
204 const sync_pb::ThemeSpecifics& theme_specifics) {
205 if (theme_specifics.use_custom_theme()) {
206 // TODO(akalin): Figure out what to do about third-party themes
207 // (i.e., those not on either Google gallery).
208 string id(theme_specifics.custom_theme_id());
209 GURL update_url(theme_specifics.custom_theme_update_url());
210 DVLOG(1) << "Applying theme " << id << " with update_url " << update_url;
Ben Murdochbb1529c2013-08-08 10:24:53 +0100211 ExtensionServiceInterface* extensions_service =
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000212 extensions::ExtensionSystem::Get(profile_)->extension_service();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000213 CHECK(extensions_service);
214 const extensions::Extension* extension =
215 extensions_service->GetExtensionById(id, true);
216 if (extension) {
217 if (!extension->is_theme()) {
218 DVLOG(1) << "Extension " << id << " is not a theme; aborting";
219 return;
220 }
Ben Murdochbb1529c2013-08-08 10:24:53 +0100221 if (!extensions_service->IsExtensionEnabled(id)) {
222 DVLOG(1) << "Theme " << id << " is not enabled; aborting";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000223 return;
224 }
225 // An enabled theme extension with the given id was found, so
226 // just set the current theme to it.
227 theme_service_->SetTheme(extension);
228 } else {
229 // No extension with this id exists -- we must install it; we do
230 // so by adding it as a pending extension and then triggering an
231 // auto-update cycle.
232 const bool kInstallSilently = true;
233 if (!extensions_service->pending_extension_manager()->AddFromSync(
234 id, update_url, &IsTheme, kInstallSilently)) {
235 LOG(WARNING) << "Could not add pending extension for " << id;
236 return;
237 }
238 extensions_service->CheckForUpdatesSoon();
239 }
240 } else if (theme_specifics.use_system_theme_by_default()) {
241 DVLOG(1) << "Switch to use native theme";
242 theme_service_->SetNativeTheme();
243 } else {
244 DVLOG(1) << "Switch to use default theme";
245 theme_service_->UseDefaultTheme();
246 }
247}
248
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000249bool ThemeSyncableService::GetThemeSpecificsFromCurrentTheme(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000250 sync_pb::ThemeSpecifics* theme_specifics) const {
251 const extensions::Extension* current_theme =
252 theme_service_->UsingDefaultTheme() ?
253 NULL :
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000254 extensions::ExtensionSystem::Get(profile_)->extension_service()->
255 GetExtensionById(theme_service_->GetThemeID(), false);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100256 if (current_theme && !extensions::sync_helper::IsSyncable(current_theme)) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000257 DVLOG(1) << "Ignoring extension from external source: " <<
258 current_theme->location();
259 return false;
260 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000261 bool use_custom_theme = (current_theme != NULL);
262 theme_specifics->set_use_custom_theme(use_custom_theme);
263 if (IsSystemThemeDistinctFromDefaultTheme()) {
264 // On platform where system theme is different from default theme, set
265 // use_system_theme_by_default to true if system theme is used, false
266 // if default system theme is used. Otherwise restore it to value in sync.
267 if (theme_service_->UsingNativeTheme()) {
268 theme_specifics->set_use_system_theme_by_default(true);
269 } else if (theme_service_->UsingDefaultTheme()) {
270 theme_specifics->set_use_system_theme_by_default(false);
271 } else {
272 theme_specifics->set_use_system_theme_by_default(
273 use_system_theme_by_default_);
274 }
275 } else {
276 // Restore use_system_theme_by_default when platform doesn't distinguish
277 // between default theme and system theme.
278 theme_specifics->set_use_system_theme_by_default(
279 use_system_theme_by_default_);
280 }
281
282 if (use_custom_theme) {
283 DCHECK(current_theme);
284 DCHECK(current_theme->is_theme());
285 theme_specifics->set_custom_theme_name(current_theme->name());
286 theme_specifics->set_custom_theme_id(current_theme->id());
287 theme_specifics->set_custom_theme_update_url(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000288 extensions::ManifestURL::GetUpdateURL(current_theme).spec());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000289 } else {
290 DCHECK(!current_theme);
291 theme_specifics->clear_custom_theme_name();
292 theme_specifics->clear_custom_theme_id();
293 theme_specifics->clear_custom_theme_update_url();
294 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000295 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000296}
297
298/* static */
299bool ThemeSyncableService::AreThemeSpecificsEqual(
300 const sync_pb::ThemeSpecifics& a,
301 const sync_pb::ThemeSpecifics& b,
302 bool is_system_theme_distinct_from_default_theme) {
303 if (a.use_custom_theme() != b.use_custom_theme()) {
304 return false;
305 }
306
307 if (a.use_custom_theme()) {
308 // We're using a custom theme, so simply compare IDs since those
309 // are guaranteed unique.
310 return a.custom_theme_id() == b.custom_theme_id();
311 } else if (is_system_theme_distinct_from_default_theme) {
312 // We're not using a custom theme, but we care about system
313 // vs. default.
314 return a.use_system_theme_by_default() == b.use_system_theme_by_default();
315 } else {
316 // We're not using a custom theme, and we don't care about system
317 // vs. default.
318 return true;
319 }
320}
321
322syncer::SyncError ThemeSyncableService::ProcessNewTheme(
323 syncer::SyncChange::SyncChangeType change_type,
324 const sync_pb::ThemeSpecifics& theme_specifics) {
325 syncer::SyncChangeList changes;
326 sync_pb::EntitySpecifics entity_specifics;
327 entity_specifics.mutable_theme()->CopyFrom(theme_specifics);
328
329 changes.push_back(
330 syncer::SyncChange(FROM_HERE, change_type,
331 syncer::SyncData::CreateLocalData(
332 kCurrentThemeClientTag, kCurrentThemeNodeTitle,
333 entity_specifics)));
334
335 DVLOG(1) << "Update theme specifics from current theme: "
336 << changes.back().ToString();
337
338 return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
339}