14e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
24e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
34e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// found in the LICENSE file.
44e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
5a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "chrome/browser/net/spdyproxy/data_reduction_proxy_settings_android.h"
68bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
74e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/android/jni_android.h"
84e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/android/jni_string.h"
94e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/android/scoped_java_ref.h"
104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/base64.h"
114e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/command_line.h"
124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/prefs/pref_service.h"
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)#include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h"
144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/prefs/proxy_prefs.h"
154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/common/chrome_switches.h"
164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/common/pref_names.h"
17a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "components/data_reduction_proxy/browser/data_reduction_proxy_settings_test_utils.h"
180f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include "testing/gmock/include/gmock/gmock.h"
194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "testing/gtest/include/gtest/gtest.h"
204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "url/gurl.h"
214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
22a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochusing testing::_;
23a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochusing testing::AnyNumber;
24a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochusing testing::Return;
25a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
26a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochconst char kDataReductionProxyDev[] = "http://foo-dev.com:80";
27a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)using data_reduction_proxy::DataReductionProxySettings;
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Used for testing the DataReductionProxySettingsAndroid class.
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class TestDataReductionProxySettingsAndroid
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    : public DataReductionProxySettingsAndroid {
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) public:
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // Constructs an Android settings object for test that wraps the provided
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // settings object.
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  explicit TestDataReductionProxySettingsAndroid(
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      DataReductionProxySettings* settings)
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      : DataReductionProxySettingsAndroid(),
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        settings_(settings) {}
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // Returns the provided setting object. Used by wrapping methods.
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  virtual DataReductionProxySettings* Settings() OVERRIDE {
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return settings_;
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // The wrapped settings object.
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  DataReductionProxySettings* settings_;
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)};
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
50a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochtemplate <class C>
5146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void data_reduction_proxy::DataReductionProxySettingsTestBase::ResetSettings(
52116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    bool allowed,
53116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    bool fallback_allowed,
54116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    bool alt_allowed,
55116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    bool promo_allowed,
56116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    bool holdback) {
5746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  int flags = 0;
5846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (allowed)
5946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    flags |= DataReductionProxyParams::kAllowed;
6046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (fallback_allowed)
6146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    flags |= DataReductionProxyParams::kFallbackAllowed;
6246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (alt_allowed)
6346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    flags |= DataReductionProxyParams::kAlternativeAllowed;
6446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (promo_allowed)
6546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    flags |= DataReductionProxyParams::kPromoAllowed;
66116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (holdback)
67116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    flags |= DataReductionProxyParams::kHoldback;
68a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  MockDataReductionProxySettings<C>* settings =
6946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      new MockDataReductionProxySettings<C>(flags);
70a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  EXPECT_CALL(*settings, GetOriginalProfilePrefs())
71a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      .Times(AnyNumber())
72a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      .WillRepeatedly(Return(&pref_service_));
73a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  EXPECT_CALL(*settings, GetLocalStatePrefs())
74a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      .Times(AnyNumber())
75a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      .WillRepeatedly(Return(&pref_service_));
766d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)  EXPECT_CALL(*settings, GetURLFetcherForAvailabilityCheck()).Times(0);
77a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  EXPECT_CALL(*settings, LogProxyState(_, _, _)).Times(0);
78a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  settings_.reset(settings);
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  settings_->SetDataReductionProxyStatisticsPrefs(statistics_prefs_.get());
80a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch}
81a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
82a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochtemplate <class C>
83a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochvoid data_reduction_proxy::DataReductionProxySettingsTestBase::SetProbeResult(
84a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    const std::string& test_url,
85a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    const std::string& response,
86a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    ProbeURLFetchResult result,
87a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    bool success,
88a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    int expected_calls)  {
89a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  MockDataReductionProxySettings<C>* settings =
90a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      static_cast<MockDataReductionProxySettings<C>*>(settings_.get());
91a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  if (0 == expected_calls) {
926d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    EXPECT_CALL(*settings, GetURLFetcherForAvailabilityCheck()).Times(0);
93a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    EXPECT_CALL(*settings, RecordProbeURLFetchResult(_)).Times(0);
94a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  } else {
95a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    EXPECT_CALL(*settings, RecordProbeURLFetchResult(result)).Times(1);
966d86b77056ed63eb6871182f42a9fd5f07550f90Torne (Richard Coles)    EXPECT_CALL(*settings, GetURLFetcherForAvailabilityCheck())
97a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        .Times(expected_calls)
98a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        .WillRepeatedly(Return(new net::FakeURLFetcher(
99a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            GURL(test_url),
100a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            settings,
101a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            response,
102a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            success ? net::HTTP_OK : net::HTTP_INTERNAL_SERVER_ERROR,
103a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            success ? net::URLRequestStatus::SUCCESS :
104a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                      net::URLRequestStatus::FAILED)));
105a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  }
106a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch}
107a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
108a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochtemplate void
109a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochdata_reduction_proxy::DataReductionProxySettingsTestBase::ResetSettings<
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    DataReductionProxyChromeSettings>(bool allowed,
11146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                       bool fallback_allowed,
11246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                       bool alt_allowed,
113116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                       bool promo_allowed,
114116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch                                       bool holdback);
115a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
116a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochtemplate void
117a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochdata_reduction_proxy::DataReductionProxySettingsTestBase::SetProbeResult<
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    DataReductionProxyChromeSettings>(const std::string& test_url,
119a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                                       const std::string& response,
120a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                                       ProbeURLFetchResult result,
121a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                                       bool success,
122a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch                                       int expected_calls);
1234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1248bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)class DataReductionProxySettingsAndroidTest
125a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    : public data_reduction_proxy::ConcreteDataReductionProxySettingsTest<
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          DataReductionProxyChromeSettings> {
1278bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) public:
1288bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)  // DataReductionProxySettingsTest implementation:
1294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  virtual void SetUp() OVERRIDE {
1304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    env_ = base::android::AttachCurrentThread();
1314e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    DataReductionProxySettingsAndroid::Register(env_);
1328bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    DataReductionProxySettingsTestBase::SetUp();
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    ResetSettingsAndroid();
1345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  void ResetSettingsAndroid() {
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    settings_android_.reset(new TestDataReductionProxySettingsAndroid(
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        settings_.get()));
1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  DataReductionProxySettings* Settings() {
1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return settings_.get();
1434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  DataReductionProxySettingsAndroid* SettingsAndroid() {
1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return settings_android_.get();
1470f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)  }
1480f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  scoped_ptr<DataReductionProxySettingsAndroid> settings_android_;
1504e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  JNIEnv* env_;
1514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)};
1524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)TEST_F(DataReductionProxySettingsAndroidTest, TestGetDataReductionProxyOrigin) {
1544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // SetUp() adds the origin to the command line, which should be returned here.
1554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  ScopedJavaLocalRef<jstring> result =
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      SettingsAndroid()->GetDataReductionProxyOrigin(env_, NULL);
1574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  ASSERT_TRUE(result.obj());
1584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  const base::android::JavaRef<jstring>& str_ref = result;
159116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  EXPECT_EQ(GURL(expected_params_->DefaultOrigin()),
16046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)            GURL(ConvertJavaStringToUTF8(str_ref)));
1614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
1624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)TEST_F(DataReductionProxySettingsAndroidTest,
1645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)       TestGetDataReductionProxyDevOrigin) {
1655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  CommandLine::ForCurrentProcess()->AppendSwitchASCII(
166a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      data_reduction_proxy::switches::kDataReductionProxyDev,
167a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch      kDataReductionProxyDev);
168116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  ResetSettings(true, true, false, true, false);
1695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  ResetSettingsAndroid();
1705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ScopedJavaLocalRef<jstring> result =
1715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      SettingsAndroid()->GetDataReductionProxyOrigin(env_, NULL);
1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ASSERT_TRUE(result.obj());
1735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const base::android::JavaRef<jstring>& str_ref = result;
17446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  EXPECT_EQ(GURL(kDataReductionProxyDev),
17546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)            GURL(ConvertJavaStringToUTF8(str_ref)));
1764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
1774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)TEST_F(DataReductionProxySettingsAndroidTest, TestGetDailyContentLengths) {
1795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  ScopedJavaLocalRef<jlongArray> result =
1805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      SettingsAndroid()->GetDailyContentLengths(
1815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          env_, data_reduction_proxy::prefs::kDailyHttpOriginalContentLength);
1824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  ASSERT_TRUE(result.obj());
1834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  jsize java_array_len = env_->GetArrayLength(result.obj());
185a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  ASSERT_EQ(static_cast<jsize>(data_reduction_proxy::kNumDaysInHistory),
186a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch            java_array_len);
1874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  jlong value;
189a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  for (size_t i = 0; i < data_reduction_proxy::kNumDaysInHistory; ++i) {
1904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    env_->GetLongArrayRegion(result.obj(), i, 1, &value);
1914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    ASSERT_EQ(
192a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch        static_cast<long>(
1935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            (data_reduction_proxy::kNumDaysInHistory - 1 - i) * 2), value);
1944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
1964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
197