supervised_user_settings_service.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "chrome/browser/supervised_user/supervised_user_settings_service.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 77dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "base/callback.h" 890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/json/json_reader.h" 990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "base/json/json_writer.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/prefs/json_pref_store.h" 115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/prefs/pref_filter.h" 12eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/strings/string_util.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/threading/sequenced_worker_pool.h" 14f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)#include "chrome/browser/supervised_user/supervised_user_url_filter.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "chrome/common/chrome_constants.h" 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "content/public/browser/browser_thread.h" 177d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)#include "content/public/browser/user_metrics.h" 1890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "sync/api/sync_change.h" 1958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "sync/api/sync_error_factory.h" 2090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)#include "sync/protocol/sync.pb.h" 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 22c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)using base::DictionaryValue; 237d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)using base::JSONReader; 245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)using base::UserMetricsAction; 25c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)using base::Value; 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)using content::BrowserThread; 27f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)using syncer::SUPERVISED_USER_SETTINGS; 2890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::ModelType; 2990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncChange; 3090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncChangeList; 3190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncChangeProcessor; 3290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncData; 3390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncDataList; 3490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncError; 3590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncErrorFactory; 3690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)using syncer::SyncMergeResult; 3790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 387d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)const char kAtomicSettings[] = "atomic_settings"; 39f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)const char kSupervisedUserInternalItemPrefix[] = "X-"; 407d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)const char kQueuedItems[] = "queued_items"; 417d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)const char kSplitSettingKeySeparator = ':'; 427d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)const char kSplitSettings[] = "split_settings"; 437d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 4458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)namespace { 4558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 4658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)bool SettingShouldApplyToPrefs(const std::string& name) { 47f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) return !StartsWithASCII(name, kSupervisedUserInternalItemPrefix, false); 4890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 4990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 5090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} // namespace 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 52f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SupervisedUserSettingsService::SupervisedUserSettingsService() 535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) : active_(false), local_settings_(new base::DictionaryValue) {} 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SupervisedUserSettingsService::~SupervisedUserSettingsService() {} 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 57f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::Init( 5858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) base::FilePath profile_path, 592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::SequencedTaskRunner* sequenced_task_runner, 6058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) bool load_synchronously) { 612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) base::FilePath path = 62f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) profile_path.Append(chrome::kSupervisedUserSettingsFilename); 635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) PersistentPrefStore* store = new JsonPrefStore( 645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) path, sequenced_task_runner, scoped_ptr<PrefFilter>()); 6558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) Init(store); 6658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (load_synchronously) 6758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) store_->ReadPrefs(); 682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) else 6958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) store_->ReadPrefsAsync(NULL); 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 72f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::Init( 7358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) scoped_refptr<PersistentPrefStore> store) { 7458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) DCHECK(!store_); 7558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) store_ = store; 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) store_->AddObserver(this); 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 79f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::Subscribe( 80f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) const SettingsCallback& callback) { 8158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (IsReady()) { 8258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) scoped_ptr<base::DictionaryValue> settings = GetSettings(); 8358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) callback.Run(settings.get()); 8458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) } 8558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 8658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) subscribers_.push_back(callback); 8758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 8858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 89f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::SetActive(bool active) { 90f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) active_ = active; 9158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) InformSubscribers(); 9258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 94f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)bool SupervisedUserSettingsService::IsReady() { 9558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return store_->IsInitializationComplete(); 9658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 9758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 98f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::Clear() { 997d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->RemoveValue(kAtomicSettings); 1007d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->RemoveValue(kSplitSettings); 101c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 102c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1037d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)// static 104f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)std::string SupervisedUserSettingsService::MakeSplitSettingKey( 1057d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) const std::string& prefix, 1067d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) const std::string& key) { 1077d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return prefix + kSplitSettingKeySeparator + key; 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 110f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::UploadItem(const std::string& key, 111f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) scoped_ptr<base::Value> value) { 11258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) DCHECK(!SettingShouldApplyToPrefs(key)); 113c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 1147d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) std::string key_suffix = key; 1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = NULL; 1167d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) if (sync_processor_) { 11758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) content::RecordAction(UserMetricsAction("ManagedUsers_UploadItem_Syncing")); 1187d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict = GetDictionaryAndSplitKey(&key_suffix); 1197d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) DCHECK(GetQueuedItems()->empty()); 1207d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) SyncChangeList change_list; 12158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) SyncData data = CreateSyncDataForSetting(key, *value); 1227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) SyncChange::SyncChangeType change_type = 1237d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->HasKey(key_suffix) ? SyncChange::ACTION_UPDATE 1247d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) : SyncChange::ACTION_ADD; 1257d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) change_list.push_back(SyncChange(FROM_HERE, change_type, data)); 1267d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) SyncError error = 1277d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) sync_processor_->ProcessSyncChanges(FROM_HERE, change_list); 1287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) DCHECK(!error.IsSet()) << error.ToString(); 1297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } else { 13058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // Queue the item up to be uploaded when we start syncing 13158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) // (in MergeDataAndStartSyncing()). 13258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) content::RecordAction(UserMetricsAction("ManagedUsers_UploadItem_Queued")); 1337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict = GetQueuedItems(); 1347d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 1357d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->SetWithoutPathExpansion(key_suffix, value.release()); 136c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)} 137c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 138f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::SetLocalSettingForTesting( 1397d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) const std::string& key, 1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) scoped_ptr<base::Value> value) { 141c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) if (value) 14258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) local_settings_->SetWithoutPathExpansion(key, value.release()); 143c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) else 14458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) local_settings_->RemoveWithoutPathExpansion(key, NULL); 145c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) 14658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) InformSubscribers(); 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1497d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)// static 150f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SyncData SupervisedUserSettingsService::CreateSyncDataForSetting( 1517d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) const std::string& name, 1525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const base::Value& value) { 1537d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) std::string json_value; 15458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) base::JSONWriter::Write(&value, &json_value); 1557d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) ::sync_pb::EntitySpecifics specifics; 1567d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) specifics.mutable_managed_user_setting()->set_name(name); 1577d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) specifics.mutable_managed_user_setting()->set_value(json_value); 1587d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return SyncData::CreateLocalData(name, name, specifics); 1597d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)} 1607d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 161f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::Shutdown() { 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) store_->RemoveObserver(this); 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SyncMergeResult SupervisedUserSettingsService::MergeDataAndStartSyncing( 16690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) ModelType type, 16790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) const SyncDataList& initial_sync_data, 16890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) scoped_ptr<SyncChangeProcessor> sync_processor, 16990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) scoped_ptr<SyncErrorFactory> error_handler) { 170f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK_EQ(SUPERVISED_USER_SETTINGS, type); 17190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) sync_processor_ = sync_processor.Pass(); 17290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) error_handler_ = error_handler.Pass(); 1737d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 1747d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) // Clear all atomic and split settings, then recreate them from Sync data. 1757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) Clear(); 17690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) for (SyncDataList::const_iterator it = initial_sync_data.begin(); 17790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) it != initial_sync_data.end(); ++it) { 178f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK_EQ(SUPERVISED_USER_SETTINGS, it->GetDataType()); 179f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) const ::sync_pb::ManagedUserSettingSpecifics& supervised_user_setting = 18090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) it->GetSpecifics().managed_user_setting(); 1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) scoped_ptr<base::Value> value( 182f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) JSONReader::Read(supervised_user_setting.value())); 183f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) std::string name_suffix = supervised_user_setting.name(); 1845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = GetDictionaryAndSplitKey(&name_suffix); 1857d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->SetWithoutPathExpansion(name_suffix, value.release()); 18690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 1877d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->ReportValueChanged(kAtomicSettings); 1887d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->ReportValueChanged(kSplitSettings); 18958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) InformSubscribers(); 19090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 1917d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) // Upload all the queued up items (either with an ADD or an UPDATE action, 1927d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) // depending on whether they already exist) and move them to split settings. 19390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) SyncChangeList change_list; 1945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* queued_items = GetQueuedItems(); 1955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::DictionaryValue::Iterator it(*queued_items); !it.IsAtEnd(); 1967d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) it.Advance()) { 1977d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) std::string key_suffix = it.key(); 1985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = GetDictionaryAndSplitKey(&key_suffix); 19958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) SyncData data = CreateSyncDataForSetting(it.key(), it.value()); 2007d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) SyncChange::SyncChangeType change_type = 2017d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->HasKey(key_suffix) ? SyncChange::ACTION_UPDATE 2027d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) : SyncChange::ACTION_ADD; 2037d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) change_list.push_back(SyncChange(FROM_HERE, change_type, data)); 2047d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->SetWithoutPathExpansion(key_suffix, it.value().DeepCopy()); 20590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 2067d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) queued_items->Clear(); 20790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 208f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) SyncMergeResult result(SUPERVISED_USER_SETTINGS); 2097d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) // Process all the accumulated changes from the queued items. 2107d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) if (change_list.size() > 0) { 2117d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->ReportValueChanged(kQueuedItems); 2127d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) result.set_error( 2137d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) sync_processor_->ProcessSyncChanges(FROM_HERE, change_list)); 2147d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 2157d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 2167d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) // TODO(bauerb): Statistics? 21790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return result; 21890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 21990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 220f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::StopSyncing(ModelType type) { 221f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK_EQ(syncer::SUPERVISED_USER_SETTINGS, type); 22290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) sync_processor_.reset(); 22390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) error_handler_.reset(); 22490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 22590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 226f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SyncDataList SupervisedUserSettingsService::GetAllSyncData( 22758537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) ModelType type) const { 228f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK_EQ(syncer::SUPERVISED_USER_SETTINGS, type); 22990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) SyncDataList data; 2305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::DictionaryValue::Iterator it(*GetAtomicSettings()); !it.IsAtEnd(); 2317d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) it.Advance()) { 23258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) data.push_back(CreateSyncDataForSetting(it.key(), it.value())); 23390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 2345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::DictionaryValue::Iterator it(*GetSplitSettings()); !it.IsAtEnd(); 2357d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) it.Advance()) { 2365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) const base::DictionaryValue* dict = NULL; 2377d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) it.value().GetAsDictionary(&dict); 2385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::DictionaryValue::Iterator jt(*dict); 2395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) !jt.IsAtEnd(); jt.Advance()) { 24058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) data.push_back(CreateSyncDataForSetting( 24158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) MakeSplitSettingKey(it.key(), jt.key()), jt.value())); 2427d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 2437d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 2447d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) DCHECK_EQ(0u, GetQueuedItems()->size()); 24590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return data; 24690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 24790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 248f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SyncError SupervisedUserSettingsService::ProcessSyncChanges( 24990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) const tracked_objects::Location& from_here, 25090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) const SyncChangeList& change_list) { 25190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) for (SyncChangeList::const_iterator it = change_list.begin(); 25290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) it != change_list.end(); ++it) { 25390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) SyncData data = it->sync_data(); 254f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) DCHECK_EQ(SUPERVISED_USER_SETTINGS, data.GetDataType()); 255f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) const ::sync_pb::ManagedUserSettingSpecifics& supervised_user_setting = 25690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) data.GetSpecifics().managed_user_setting(); 257f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) std::string key = supervised_user_setting.name(); 2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = GetDictionaryAndSplitKey(&key); 25990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) switch (it->change_type()) { 26090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) case SyncChange::ACTION_ADD: 26190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) case SyncChange::ACTION_UPDATE: { 2625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) scoped_ptr<base::Value> value( 263f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles) JSONReader::Read(supervised_user_setting.value())); 2647d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) if (dict->HasKey(key)) { 26590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) DLOG_IF(WARNING, it->change_type() == SyncChange::ACTION_ADD) 2667d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) << "Value for key " << key << " already exists"; 26790dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } else { 26890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) DLOG_IF(WARNING, it->change_type() == SyncChange::ACTION_UPDATE) 2697d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) << "Value for key " << key << " doesn't exist yet"; 27090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 2717d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->SetWithoutPathExpansion(key, value.release()); 27290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) break; 27390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 27490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) case SyncChange::ACTION_DELETE: { 2757d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) DLOG_IF(WARNING, !dict->HasKey(key)) << "Trying to delete nonexistent " 2767d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) << "key " << key; 2777d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict->RemoveWithoutPathExpansion(key, NULL); 27890dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) break; 27990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 28090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) case SyncChange::ACTION_INVALID: { 28190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) NOTREACHED(); 28290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) break; 28390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 28490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 28590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) } 2867d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->ReportValueChanged(kAtomicSettings); 2877d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->ReportValueChanged(kSplitSettings); 28858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) InformSubscribers(); 2897d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 2907d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) SyncError error; 29190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) return error; 29290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)} 29390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) 294f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::OnPrefValueChanged(const std::string& key) { 295f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)} 29658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 297f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::OnInitializationCompleted(bool success) { 29858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) DCHECK(success); 29958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) DCHECK(IsReady()); 30058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) InformSubscribers(); 3017d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)} 3027d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 303f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)base::DictionaryValue* SupervisedUserSettingsService::GetOrCreateDictionary( 3047d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) const std::string& key) const { 3055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::Value* value = NULL; 3065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = NULL; 3077d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) if (store_->GetMutableValue(key, &value)) { 3087d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) bool success = value->GetAsDictionary(&dict); 3097d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) DCHECK(success); 3107d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } else { 3117d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) dict = new base::DictionaryValue; 3127d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) store_->SetValue(key, dict); 3137d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3157d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return dict; 3167d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)} 3177d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 318f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)base::DictionaryValue* 319f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)SupervisedUserSettingsService::GetAtomicSettings() const { 3207d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return GetOrCreateDictionary(kAtomicSettings); 3217d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)} 3227d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 323f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)base::DictionaryValue* SupervisedUserSettingsService::GetSplitSettings() const { 3247d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return GetOrCreateDictionary(kSplitSettings); 3257d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)} 3267d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 327f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)base::DictionaryValue* SupervisedUserSettingsService::GetQueuedItems() const { 3287d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return GetOrCreateDictionary(kQueuedItems); 3297d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles)} 3307d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 331f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)base::DictionaryValue* SupervisedUserSettingsService::GetDictionaryAndSplitKey( 3327d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) std::string* key) const { 3337d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) size_t pos = key->find_first_of(kSplitSettingKeySeparator); 3347d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) if (pos == std::string::npos) 3357d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) return GetAtomicSettings(); 3367d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 3375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* split_settings = GetSplitSettings(); 3387d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) std::string prefix = key->substr(0, pos); 3395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* dict = NULL; 3407d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) if (!split_settings->GetDictionary(prefix, &dict)) { 3415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) dict = new base::DictionaryValue; 3427d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) DCHECK(!split_settings->HasKey(prefix)); 3437d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) split_settings->Set(prefix, dict); 3447d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 3457d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) key->erase(0, pos + 1); 3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return dict; 3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 349f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)scoped_ptr<base::DictionaryValue> SupervisedUserSettingsService::GetSettings() { 35058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) DCHECK(IsReady()); 35158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (!active_) 35258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return scoped_ptr<base::DictionaryValue>(); 35358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 3545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) scoped_ptr<base::DictionaryValue> settings(local_settings_->DeepCopy()); 3557d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 3565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* atomic_settings = GetAtomicSettings(); 3575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::DictionaryValue::Iterator it(*atomic_settings); !it.IsAtEnd(); 3587d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) it.Advance()) { 35958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (!SettingShouldApplyToPrefs(it.key())) 3607d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) continue; 3617d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 36258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) settings->Set(it.key(), it.value().DeepCopy()); 3637d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) } 3647d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 3655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::DictionaryValue* split_settings = GetSplitSettings(); 3665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) for (base::DictionaryValue::Iterator it(*split_settings); !it.IsAtEnd(); 3677d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) it.Advance()) { 36858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (!SettingShouldApplyToPrefs(it.key())) 3697d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) continue; 3707d4cd473f85ac64c3747c96c277f9e506a0d2246Torne (Richard Coles) 37158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) settings->Set(it.key(), it.value().DeepCopy()); 372c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles) } 37358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 37458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return settings.Pass(); 3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 377f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)void SupervisedUserSettingsService::InformSubscribers() { 37858537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) if (!IsReady()) 37958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return; 38058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 38158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) scoped_ptr<base::DictionaryValue> settings = GetSettings(); 38258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) for (std::vector<SettingsCallback>::iterator it = subscribers_.begin(); 38358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) it != subscribers_.end(); ++it) { 38458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) it->Run(settings.get()); 38558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) } 38658537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 387