Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(493)

Side by Side Diff: chromeos/quirks_client/quirks_client.cc

Issue 1528963002: Quirks Client for downloading and providing display profiles (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // 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 "chromeos/quirks_client/quirks_client.h"
6
7 #include "base/base64.h"
8 #include "base/command_line.h"
9 #include "base/files/file_util.h"
10 #include "base/format_macros.h"
11 #include "base/json/json_reader.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/path_service.h"
14 #include "base/prefs/pref_registry_simple.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/prefs/scoped_user_pref_update.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/sys_info.h"
19 #include "base/task_runner_util.h"
20 #include "base/thread_task_runner_handle.h"
21 #include "chromeos/chromeos_paths.h"
22 #include "chromeos/chromeos_pref_names.h"
23 #include "chromeos/chromeos_switches.h"
24 #include "net/base/load_flags.h"
25 #include "net/http/http_status_code.h"
26 #include "net/url_request/url_fetcher.h"
27 #include "net/url_request/url_request_context_getter.h"
28
29 namespace chromeos {
30
31 namespace {
32
33 static QuirksClientBrowserDelegate* g_browser_delegate = nullptr;
34
35 const char kQuirksUrlFormat[] =
36 "https://qa-quirksserver-pa.sandbox.googleapis.com/v2/display/%08" PRIx64
37 "/clients/chromeos/M%d";
38
39 // How often we query Quirks Server.
40 const int kDaysBetweenServerChecks = 30;
41
42 // Sleep between Quirks retries (used multiplied by squared retry number).
43 const unsigned kRetrySleepSeconds = 10;
44
45 // Retry is infinite with increasing intervals. When calculated delay becomes
46 // longer than maximum (kMaxRetrySleepSeconds) it is set to the maximum.
47 const double kMaxRetrySleepSeconds = 6 * 3600; // 6 hours
48
49 // TODO!!! We're still sorting out the details of which key to use. This code
50 // is just here temporarily replacing the currently working api key.
51 std::string api_key_quirks() {
52 return std::string();
53 }
54
55 std::string id2str(int64_t product_id) {
56 return base::StringPrintf("%08" PRIx64, product_id);
57 }
58
59 // Check command line switch; the Quirks Client is current disabled by default.
60 bool QuirksClientEnabled() {
61 return base::CommandLine::ForCurrentProcess()->HasSwitch(
62 switches::kEnableDisplayQuirksClient);
63 }
64
65 // Check if file exists, VLOG results.
66 bool CheckAndLogFile(const base::FilePath& path) {
67 const bool exists = base::PathExists(path);
68 VLOG(1) << (exists ? "File" : "No File") << " found at " << path.value();
69 // TODO(glevin): If file exists, do we want to implement a hash to verify that
70 // the file hasn't been corrupted or tampered with?
71 return exists;
72 }
73
74 // Returns the path to the writable display profile directory.
75 // On chrome device, returns /var/cache/display_profiles.
76 // On Linux desktop, returns {DIR_USER_DATA}/display_profiles.
77 // Either directory must be created beforehand.
78 static base::FilePath GetDisplayProfileDirectory() {
79 base::FilePath directory;
80 if (base::SysInfo::IsRunningOnChromeOS()) {
81 PathService::Get(chromeos::DIR_DEVICE_DISPLAY_PROFILES, &directory);
82 } else {
83 // TODO!!! Fix this once this is moved to somewhere that we can include
84 // /chrome/ for |chrome::DIR_USER_DATA|.
85 const int DIR_USER_DATA_FAKE = 1002;
86 PathService::Get(DIR_USER_DATA_FAKE, &directory);
87 // PathService::Get(chrome::DIR_USER_DATA, &directory);
88 directory = directory.Append("display_profiles");
89 }
90 return directory;
91 }
92
93 // Initializes QuirksClient object on the browser thread, where most of its work
94 // needs to be done.
95 void RunClientOnBrowserThread(
96 int64_t product_id,
97 QuirksClient::DownloadFinishedCallback* on_download_finished) {
98 if (!g_browser_delegate || !g_browser_delegate->Validate()) {
99 VLOG(1) << "Browser delegate not initialized; can't start Quirks Client.";
100 return;
101 }
102
103 const base::Time last_check =
104 g_browser_delegate->GetLastServerCheck(product_id);
105 const base::TimeDelta time_since = base::Time::Now() - last_check;
106
107 // If we haven't checked Quirks Server in the last 30 days, create a
108 // QuirksClient to check Quirks Server for icc file or updates.
109 if (time_since > base::TimeDelta::FromDays(kDaysBetweenServerChecks)) {
110 // TODO(glevin): Do we want to keep a pointer to this, or just let it roam
111 // free and delete itself when it's done?
112 /* Might need to handle interaction with delegate more intelligently. If
113 * offline, the client might persist, and keep trying. If it tries during
114 * shutdown, while the delegate is invalidating, it might cause trouble. */
115 QuirksClient* quirks_client =
116 new QuirksClient(product_id, on_download_finished);
117 quirks_client->Start();
118 } else {
119 VLOG(2) << time_since.InDays()
120 << " days since last Quirks Server check for display "
121 << id2str(product_id);
122 }
123 }
124
125 } // namespace
126
127 ////////////////////////////////////////////////////////////////////////////////
128 // QuirksClient
129
130 QuirksClient::QuirksClient(int64_t product_id,
131 DownloadFinishedCallback* on_download_finished)
132 : product_id_(product_id),
133 on_download_finished_(on_download_finished),
134 icc_path_(GetDisplayProfileDirectory().Append(id2str(product_id) +
135 ".icc")),
136 request_reason_(FIRST),
137 retries_(0),
138 retry_delay_(base::TimeDelta::FromSeconds(kRetrySleepSeconds)),
139 weak_ptr_factory_(this) {}
140
141 QuirksClient::~QuirksClient() {}
142
143 // static
144 base::FilePath QuirksClient::RequestIccProfilePath(
145 int64_t product_id,
146 DownloadFinishedCallback* on_download_finished) {
147 std::string file_name = id2str(product_id) + ".icc";
148
149 // First, look for icc file in old read-only location. If there, we don't use
150 // the Quirks server.
151 // TODO(glevin): Awaiting final decision on how to handle these files.
152 base::FilePath path;
153 CHECK(
154 PathService::Get(chromeos::DIR_DEVICE_COLOR_CALIBRATION_PROFILES, &path));
155 path = path.Append(file_name);
156 if (CheckAndLogFile(path))
157 return path;
158
159 // If Quirks Client is disabled, no other icc file is available.
160 if (!QuirksClientEnabled()) {
161 VLOG(1) << "Quirks client disabled, no built-in icc file available.";
162 return base::FilePath();
163 }
164
165 // Check if QuirksClient has already downloaded icc file from server.
166 path = GetDisplayProfileDirectory().Append(file_name);
167 if (CheckAndLogFile(path))
168 return path;
169
170 // TODO(glevin): Eventually we'll want to check the server for updates to
171 // files, so we'll still want to get down here even if we find the icc file.
172
173 g_browser_delegate->RunClient(product_id, on_download_finished);
174
175 return path;
176 }
177
178 void QuirksClient::Start() {
179 // DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
180
181 // URL of icc file on Quirks Server.
182 // TODO(glevin): Replace |44| with actual version. This is fine for now, as
183 // the Quirks Server is not currently using this value.
184 std::string url = base::StringPrintf(kQuirksUrlFormat, product_id_, 44);
185
186 VLOG(2) << "Preparing to download\n " << url << "\nto file "
187 << icc_path_.value();
188
189 url += "?key=";
190 url += api_key_quirks();
191
192 url_fetcher_ = net::URLFetcher::Create(GURL(url), net::URLFetcher::GET, this);
193 url_fetcher_->SetRequestContext(g_browser_delegate->url_context_getter());
194 url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
195 net::LOAD_DISABLE_CACHE |
196 net::LOAD_DO_NOT_SAVE_COOKIES |
197 net::LOAD_DO_NOT_SEND_COOKIES |
198 net::LOAD_DO_NOT_SEND_AUTH_DATA);
199 url_fetcher_->Start();
200 }
201
202 void QuirksClient::OnURLFetchComplete(const net::URLFetcher* source) {
203 // DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
204 DCHECK_EQ(url_fetcher_.get(), source);
205
206 const net::URLRequestStatus status = source->GetStatus();
207 const int response_code = source->GetResponseCode();
208 const bool server_error =
209 !status.is_success() ||
210 (response_code >= net::HTTP_INTERNAL_SERVER_ERROR &&
211 response_code < (net::HTTP_INTERNAL_SERVER_ERROR + 100));
212
213 VLOG(2) << "QuirksClient::OnURLFetchComplete():"
214 << " status=" << status.status()
215 << ", response_code=" << response_code
216 << ", server_error=" << server_error;
217
218 g_browser_delegate->RecordReasonUmaStat(request_reason_);
219
220 if (response_code == net::HTTP_NOT_FOUND) {
221 VLOG(1) << id2str(product_id_) << ".icc not found on Quirks server.";
222 g_browser_delegate->RecordFileFoundUmaStat(false);
223 Shutdown();
224 return;
225 }
226
227 if (server_error) {
228 url_fetcher_.reset();
229 Retry();
230 return;
231 }
232
233 g_browser_delegate->RecordFileFoundUmaStat(true);
234 std::string response;
235 url_fetcher_->GetResponseAsString(&response);
236 VLOG(2) << "Quirks server response:\n" << response;
237
238 // Parse response data and write to file on io thread.
239 std::string data;
240 if (ParseResult(response, &data)) {
241 base::PostTaskAndReplyWithResult(
242 g_browser_delegate->blocking_pool(), FROM_HERE,
243 base::Bind(&WriteIccFile, icc_path_, data),
244 base::Bind(&QuirksClient::OnWriteIccFileFinished,
245 weak_ptr_factory_.GetWeakPtr()));
246 }
247 }
248
249 void QuirksClient::Shutdown() {
250 g_browser_delegate->SetLastServerCheck(product_id_, base::Time::Now());
251 g_browser_delegate->message_loop_ui()->DeleteSoon(FROM_HERE, this);
252 }
253
254 void QuirksClient::Retry() {
255 // DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
256 ++retries_;
257
258 const double delay_seconds =
259 std::min(kMaxRetrySleepSeconds, static_cast<double>(retries_) * retries_ *
260 retry_delay_.InSecondsF());
261 const base::TimeDelta delay = base::TimeDelta::FromSecondsD(delay_seconds);
262
263 VLOG(1) << "Schedule next Quirks download attempt in " << delay.InSecondsF()
264 << " seconds (retry = " << retries_ << ").";
265 retry_current_delay_ = delay;
266 request_scheduled_.Start(FROM_HERE, delay, this, &QuirksClient::Start);
267 }
268
269 // static
270 bool QuirksClient::WriteIccFile(const base::FilePath file_path,
271 const std::string& data) {
272 int bytes_written = base::WriteFile(file_path, data.data(), data.length());
273 if (bytes_written == -1)
274 VLOG(1) << "Failed to write: " << file_path.value() << ", err = " << errno;
275 else
276 VLOG(1) << bytes_written << "bytes written to: " << file_path.value();
277
278 return (bytes_written != -1);
279 }
280
281 void QuirksClient::OnWriteIccFileFinished(bool success) {
282 if (success) {
283 g_browser_delegate->SetLastServerCheck(product_id_, base::Time::Now());
284 if (on_download_finished_)
285 on_download_finished_->Run(icc_path_);
286 }
287 // If we got data from server but couldn't write it, that's not likely to
288 // change this session, so give up.
289 Shutdown();
290 }
291
292 bool QuirksClient::ParseResult(const std::string& result, std::string* data) {
293 std::string data64;
294 const base::DictionaryValue* dict;
295 scoped_ptr<base::Value> json = base::JSONReader::Read(result);
296 if (!json || !json->GetAsDictionary(&dict) ||
297 !dict->GetString("icc", &data64)) {
298 VLOG(1) << "Failed to parse JSON icc data";
299 return false;
300 }
301
302 if (!base::Base64Decode(data64, data)) {
303 VLOG(1) << "Failed to decode Base64 icc data";
304 return false;
305 }
306
307 return true;
308 }
309
310 ////////////////////////////////////////////////////////////////////////////////
311 // QuirksClientBrowserDelegate
312
313 // static
314 void QuirksClientBrowserDelegate::Initialize(
315 base::MessageLoopForUI* message_loop_ui,
316 base::SequencedWorkerPool* blocking_pool,
317 PrefService* local_state,
318 net::URLRequestContextGetter* url_context_getter) {
319 if (!QuirksClientEnabled())
320 return;
321 g_browser_delegate = new QuirksClientBrowserDelegate(
322 message_loop_ui, blocking_pool, local_state, url_context_getter);
323 }
324
325 // static
326 void QuirksClientBrowserDelegate::Shutdown() {
327 delete g_browser_delegate;
328 g_browser_delegate = nullptr;
329 }
330
331 // static
332 void QuirksClientBrowserDelegate::RegisterPrefs(PrefRegistrySimple* registry) {
333 registry->RegisterDictionaryPref(prefs::kQuirksClientLastServerCheck);
334 }
335
336 void QuirksClientBrowserDelegate::RunClient(
337 int64_t product_id,
338 QuirksClient::DownloadFinishedCallback* on_download_finished) {
339 message_loop_ui_->PostTask(
340 FROM_HERE,
341 base::Bind(&RunClientOnBrowserThread, product_id, on_download_finished));
342 }
343
344 base::Time QuirksClientBrowserDelegate::GetLastServerCheck(int64_t product_id) {
345 double last_check = 0.0;
346 const base::DictionaryValue* dict =
347 local_state_->GetDictionary(prefs::kQuirksClientLastServerCheck);
348 if (dict)
349 dict->GetDouble(id2str(product_id), &last_check);
350 // TODO!!! if (last_check == 0), set last_check = rand[-30,0] days. That is,
351 // stagger initial server call over 30 day interval, unless machine is
352 // < 1 month old.
353 return base::Time::FromDoubleT(last_check);
354 }
355
356 void QuirksClientBrowserDelegate::SetLastServerCheck(int64_t product_id,
357 base::Time last_check) {
358 DictionaryPrefUpdate dict(local_state_, prefs::kQuirksClientLastServerCheck);
359 dict->SetDouble(id2str(product_id), last_check.ToDoubleT());
360 }
361
362 // TODO(glevin): Add code to record UMA stats here. Also need to set
363 // request_reason_ in QuirksClient.
364 void QuirksClientBrowserDelegate::RecordReasonUmaStat(
365 QuirksClient::RequestReason reason) {}
366
367 void QuirksClientBrowserDelegate::RecordFileFoundUmaStat(bool success) {}
368
369 bool QuirksClientBrowserDelegate::Validate() {
370 return base::MessageLoopForUI::current()->is_running() &&
371 local_state_->CalledOnValidThread() &&
372 base::ThreadTaskRunnerHandle::IsSet();
373 }
374
375 QuirksClientBrowserDelegate::QuirksClientBrowserDelegate(
376 base::MessageLoopForUI* message_loop_ui,
377 base::SequencedWorkerPool* blocking_pool,
378 PrefService* local_state,
379 net::URLRequestContextGetter* url_context_getter)
380 : message_loop_ui_(message_loop_ui),
381 blocking_pool_(blocking_pool),
382 local_state_(local_state),
383 url_context_getter_(url_context_getter) {}
384
385 QuirksClientBrowserDelegate::~QuirksClientBrowserDelegate() {}
386
387 } // namespace chromeos
OLDNEW
« chromeos/chromeos_pref_names.h ('K') | « chromeos/quirks_client/quirks_client.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698