web_resource_service.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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/web_resource/web_resource_service.h"
6
7#include "base/command_line.h"
8#include "base/file_path.h"
9#include "base/string_util.h"
10#include "base/string_number_conversions.h"
11#include "base/time.h"
12#include "base/utf_string_conversions.h"
13#include "base/values.h"
14#include "chrome/browser/browser_process.h"
15#include "chrome/browser/browser_thread.h"
16#include "chrome/browser/profile.h"
17#include "chrome/common/chrome_switches.h"
18#include "chrome/common/net/url_fetcher.h"
19#include "chrome/common/notification_service.h"
20#include "chrome/common/notification_type.h"
21#include "chrome/common/pref_names.h"
22#include "googleurl/src/gurl.h"
23#include "net/base/load_flags.h"
24#include "net/url_request/url_request_status.h"
25
26const char* WebResourceService::kCurrentTipPrefName = "current_tip";
27const char* WebResourceService::kTipCachePrefName = "tips";
28
29class WebResourceService::WebResourceFetcher
30    : public URLFetcher::Delegate {
31 public:
32  explicit WebResourceFetcher(WebResourceService* web_resource_service) :
33      ALLOW_THIS_IN_INITIALIZER_LIST(fetcher_factory_(this)),
34      web_resource_service_(web_resource_service) {
35  }
36
37  // Delay initial load of resource data into cache so as not to interfere
38  // with startup time.
39  void StartAfterDelay(int delay_ms) {
40    MessageLoop::current()->PostDelayedTask(FROM_HERE,
41      fetcher_factory_.NewRunnableMethod(&WebResourceFetcher::StartFetch),
42                                         delay_ms);
43  }
44
45  // Initializes the fetching of data from the resource server.  Data
46  // load calls OnURLFetchComplete.
47  void StartFetch() {
48    // Balanced in OnURLFetchComplete.
49    web_resource_service_->AddRef();
50    // First, put our next cache load on the MessageLoop.
51    MessageLoop::current()->PostDelayedTask(FROM_HERE,
52      fetcher_factory_.NewRunnableMethod(&WebResourceFetcher::StartFetch),
53                                         kCacheUpdateDelay);
54    // If we are still fetching data, exit.
55    if (web_resource_service_->in_fetch_)
56      return;
57    else
58      web_resource_service_->in_fetch_ = true;
59
60    url_fetcher_.reset(new URLFetcher(GURL(
61        web_resource_service_->web_resource_server_),
62        URLFetcher::GET, this));
63    // Do not let url fetcher affect existing state in profile (by setting
64    // cookies, for example.
65    url_fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE |
66      net::LOAD_DO_NOT_SAVE_COOKIES);
67    url_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
68    url_fetcher_->Start();
69  }
70
71  // From URLFetcher::Delegate.
72  void OnURLFetchComplete(const URLFetcher* source,
73                          const GURL& url,
74                          const URLRequestStatus& status,
75                          int response_code,
76                          const ResponseCookies& cookies,
77                          const std::string& data) {
78    // Delete the URLFetcher when this function exits.
79    scoped_ptr<URLFetcher> clean_up_fetcher(url_fetcher_.release());
80
81    // Don't parse data if attempt to download was unsuccessful.
82    // Stop loading new web resource data, and silently exit.
83    if (!status.is_success() || (response_code != 200))
84      return;
85
86    web_resource_service_->UpdateResourceCache(data);
87    web_resource_service_->Release();
88  }
89
90 private:
91  // So that we can delay our start so as not to affect start-up time; also,
92  // so that we can schedule future cache updates.
93  ScopedRunnableMethodFactory<WebResourceFetcher> fetcher_factory_;
94
95  // The tool that fetches the url data from the server.
96  scoped_ptr<URLFetcher> url_fetcher_;
97
98  // Our owner and creator. Ref counted.
99  WebResourceService* web_resource_service_;
100};
101
102// This class coordinates a web resource unpack and parse task which is run in
103// a separate process.  Results are sent back to this class and routed to
104// the WebResourceService.
105class WebResourceService::UnpackerClient
106    : public UtilityProcessHost::Client {
107 public:
108  UnpackerClient(WebResourceService* web_resource_service,
109                 const std::string& json_data)
110    : web_resource_service_(web_resource_service),
111      json_data_(json_data), got_response_(false) {
112  }
113
114  void Start() {
115    AddRef();  // balanced in Cleanup.
116
117    // If we don't have a resource_dispatcher_host_, assume we're in
118    // a test and run the unpacker directly in-process.
119    bool use_utility_process =
120        web_resource_service_->resource_dispatcher_host_ != NULL &&
121        !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
122    if (use_utility_process) {
123      BrowserThread::ID thread_id;
124      CHECK(BrowserThread::GetCurrentThreadIdentifier(&thread_id));
125      BrowserThread::PostTask(
126          BrowserThread::IO, FROM_HERE,
127          NewRunnableMethod(this, &UnpackerClient::StartProcessOnIOThread,
128                            web_resource_service_->resource_dispatcher_host_,
129                            thread_id));
130    } else {
131      WebResourceUnpacker unpacker(json_data_);
132      if (unpacker.Run()) {
133        OnUnpackWebResourceSucceeded(*unpacker.parsed_json());
134      } else {
135        OnUnpackWebResourceFailed(unpacker.error_message());
136      }
137    }
138  }
139
140 private:
141  ~UnpackerClient() {}
142
143  // UtilityProcessHost::Client
144  virtual void OnProcessCrashed() {
145    if (got_response_)
146      return;
147
148    OnUnpackWebResourceFailed(
149        "Chrome crashed while trying to retrieve web resources.");
150  }
151
152  virtual void OnUnpackWebResourceSucceeded(
153      const DictionaryValue& parsed_json) {
154    web_resource_service_->OnWebResourceUnpacked(parsed_json);
155    Cleanup();
156  }
157
158  virtual void OnUnpackWebResourceFailed(const std::string& error_message) {
159    web_resource_service_->EndFetch();
160    Cleanup();
161  }
162
163  // Release reference and set got_response_.
164  void Cleanup() {
165    if (got_response_)
166      return;
167
168    got_response_ = true;
169    Release();
170  }
171
172  void StartProcessOnIOThread(ResourceDispatcherHost* rdh,
173                              BrowserThread::ID thread_id) {
174    UtilityProcessHost* host = new UtilityProcessHost(rdh, this, thread_id);
175    // TODO(mrc): get proper file path when we start using web resources
176    // that need to be unpacked.
177    host->StartWebResourceUnpacker(json_data_);
178  }
179
180  scoped_refptr<WebResourceService> web_resource_service_;
181
182  // Holds raw JSON string.
183  const std::string& json_data_;
184
185  // True if we got a response from the utility process and have cleaned up
186  // already.
187  bool got_response_;
188};
189
190// Server for custom logo signals.
191const char* WebResourceService::kDefaultResourceServer =
192    "https://www.google.com/support/chrome/bin/topic/30248/inproduct";
193
194WebResourceService::WebResourceService(Profile* profile)
195    : prefs_(profile->GetPrefs()),
196      in_fetch_(false) {
197  Init();
198}
199
200WebResourceService::~WebResourceService() { }
201
202void WebResourceService::Init() {
203  resource_dispatcher_host_ = g_browser_process->resource_dispatcher_host();
204  web_resource_fetcher_.reset(new WebResourceFetcher(this));
205  prefs_->RegisterStringPref(prefs::kNTPWebResourceCacheUpdate, "0");
206  prefs_->RegisterRealPref(prefs::kNTPCustomLogoStart, 0);
207  prefs_->RegisterRealPref(prefs::kNTPCustomLogoEnd, 0);
208
209  if (prefs_->HasPrefPath(prefs::kNTPLogoResourceServer)) {
210    web_resource_server_ = prefs_->GetString(prefs::kNTPLogoResourceServer);
211    return;
212  }
213
214  // If we have not yet set a server, reset and force an immediate update.
215  web_resource_server_ = kDefaultResourceServer;
216  prefs_->SetString(prefs::kNTPWebResourceCacheUpdate, "");
217}
218
219void WebResourceService::EndFetch() {
220  in_fetch_ = false;
221}
222
223void WebResourceService::OnWebResourceUnpacked(
224  const DictionaryValue& parsed_json) {
225  UnpackLogoSignal(parsed_json);
226  EndFetch();
227}
228
229void WebResourceService::StartAfterDelay() {
230  int delay = kStartResourceFetchDelay;
231  // Check whether we have ever put a value in the web resource cache;
232  // if so, pull it out and see if it's time to update again.
233  if (prefs_->HasPrefPath(prefs::kNTPWebResourceCacheUpdate)) {
234    std::string last_update_pref =
235        prefs_->GetString(prefs::kNTPWebResourceCacheUpdate);
236    if (!last_update_pref.empty()) {
237      double last_update_value;
238      base::StringToDouble(last_update_pref, &last_update_value);
239      int ms_until_update = kCacheUpdateDelay -
240          static_cast<int>((base::Time::Now() - base::Time::FromDoubleT(
241          last_update_value)).InMilliseconds());
242
243      delay = ms_until_update > kCacheUpdateDelay ?
244              kCacheUpdateDelay : (ms_until_update < kStartResourceFetchDelay ?
245                                   kStartResourceFetchDelay : ms_until_update);
246    }
247  }
248
249  // Start fetch and wait for UpdateResourceCache.
250  web_resource_fetcher_->StartAfterDelay(static_cast<int>(delay));
251}
252
253void WebResourceService::UpdateResourceCache(const std::string& json_data) {
254  UnpackerClient* client = new UnpackerClient(this, json_data);
255  client->Start();
256
257  // Update resource server and cache update time in preferences.
258  prefs_->SetString(prefs::kNTPWebResourceCacheUpdate,
259      base::DoubleToString(base::Time::Now().ToDoubleT()));
260  prefs_->SetString(prefs::kNTPLogoResourceServer, web_resource_server_);
261}
262
263void WebResourceService::UnpackTips(const DictionaryValue& parsed_json) {
264  // Get dictionary of cached preferences.
265  web_resource_cache_ =
266      prefs_->GetMutableDictionary(prefs::kNTPWebResourceCache);
267
268  // The list of individual tips.
269  ListValue* tip_holder = new ListValue();
270  web_resource_cache_->Set(WebResourceService::kTipCachePrefName, tip_holder);
271
272  DictionaryValue* topic_dict;
273  ListValue* answer_list;
274  std::string topic_id;
275  std::string answer_id;
276  std::string inproduct;
277  int tip_counter = 0;
278
279  if (parsed_json.GetDictionary("topic", &topic_dict)) {
280    if (topic_dict->GetString("topic_id", &topic_id))
281      web_resource_cache_->SetString("topic_id", topic_id);
282    if (topic_dict->GetList("answers", &answer_list)) {
283      for (ListValue::const_iterator tip_iter = answer_list->begin();
284           tip_iter != answer_list->end(); ++tip_iter) {
285        if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY))
286          continue;
287        DictionaryValue* a_dic =
288            static_cast<DictionaryValue*>(*tip_iter);
289        if (a_dic->GetString("inproduct", &inproduct)) {
290          tip_holder->Append(Value::CreateStringValue(inproduct));
291        }
292        tip_counter++;
293      }
294      // If tips exist, set current index to 0.
295      if (!inproduct.empty()) {
296        web_resource_cache_->SetInteger(
297          WebResourceService::kCurrentTipPrefName, 0);
298      }
299    }
300  }
301}
302
303void WebResourceService::UnpackLogoSignal(const DictionaryValue& parsed_json) {
304  DictionaryValue* topic_dict;
305  ListValue* answer_list;
306  double old_logo_start = 0;
307  double old_logo_end = 0;
308  double logo_start = 0;
309  double logo_end = 0;
310
311  // Check for preexisting start and end values.
312  if (prefs_->HasPrefPath(prefs::kNTPCustomLogoStart) &&
313      prefs_->HasPrefPath(prefs::kNTPCustomLogoEnd)) {
314    old_logo_start = prefs_->GetReal(prefs::kNTPCustomLogoStart);
315    old_logo_end = prefs_->GetReal(prefs::kNTPCustomLogoEnd);
316  }
317
318  // Check for newly received start and end values.
319  if (parsed_json.GetDictionary("topic", &topic_dict)) {
320    if (topic_dict->GetList("answers", &answer_list)) {
321      std::string logo_start_string = "";
322      std::string logo_end_string = "";
323      for (ListValue::const_iterator tip_iter = answer_list->begin();
324           tip_iter != answer_list->end(); ++tip_iter) {
325        if (!(*tip_iter)->IsType(Value::TYPE_DICTIONARY))
326          continue;
327        DictionaryValue* a_dic =
328            static_cast<DictionaryValue*>(*tip_iter);
329        std::string logo_signal;
330        if (a_dic->GetString("name", &logo_signal)) {
331          if (logo_signal == "custom_logo_start") {
332            a_dic->GetString("inproduct", &logo_start_string);
333          } else if (logo_signal == "custom_logo_end") {
334            a_dic->GetString("inproduct", &logo_end_string);
335          }
336        }
337      }
338      if (!logo_start_string.empty() &&
339          logo_start_string.length() > 0 &&
340          !logo_end_string.empty() &&
341          logo_end_string.length() > 0) {
342        base::Time start_time;
343        base::Time end_time;
344        if (base::Time::FromString(
345                ASCIIToWide(logo_start_string).c_str(), &start_time) &&
346            base::Time::FromString(
347                ASCIIToWide(logo_end_string).c_str(), &end_time)) {
348          logo_start = start_time.ToDoubleT();
349          logo_end = end_time.ToDoubleT();
350        }
351      }
352    }
353  }
354
355  // If logo start or end times have changed, trigger a new web resource
356  // notification, so that the logo on the NTP is updated. This check is
357  // outside the reading of the web resource data, because the absence of
358  // dates counts as a triggering change if there were dates before.
359  if (!(old_logo_start == logo_start) ||
360      !(old_logo_end == logo_end)) {
361    prefs_->SetReal(prefs::kNTPCustomLogoStart, logo_start);
362    prefs_->SetReal(prefs::kNTPCustomLogoEnd, logo_end);
363    NotificationService* service = NotificationService::current();
364    service->Notify(NotificationType::WEB_RESOURCE_AVAILABLE,
365                    Source<WebResourceService>(this),
366                    NotificationService::NoDetails());
367  }
368}
369