| Index: chromeos/quirks_client/quirks_client.cc
|
| diff --git a/chromeos/quirks_client/quirks_client.cc b/chromeos/quirks_client/quirks_client.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0a09c51090018f02428f49828bac2cf4eeead449
|
| --- /dev/null
|
| +++ b/chromeos/quirks_client/quirks_client.cc
|
| @@ -0,0 +1,387 @@
|
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chromeos/quirks_client/quirks_client.h"
|
| +
|
| +#include "base/base64.h"
|
| +#include "base/command_line.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/format_macros.h"
|
| +#include "base/json/json_reader.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/path_service.h"
|
| +#include "base/prefs/pref_registry_simple.h"
|
| +#include "base/prefs/pref_service.h"
|
| +#include "base/prefs/scoped_user_pref_update.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "base/sys_info.h"
|
| +#include "base/task_runner_util.h"
|
| +#include "base/thread_task_runner_handle.h"
|
| +#include "chromeos/chromeos_paths.h"
|
| +#include "chromeos/chromeos_pref_names.h"
|
| +#include "chromeos/chromeos_switches.h"
|
| +#include "net/base/load_flags.h"
|
| +#include "net/http/http_status_code.h"
|
| +#include "net/url_request/url_fetcher.h"
|
| +#include "net/url_request/url_request_context_getter.h"
|
| +
|
| +namespace chromeos {
|
| +
|
| +namespace {
|
| +
|
| +static QuirksClientBrowserDelegate* g_browser_delegate = nullptr;
|
| +
|
| +const char kQuirksUrlFormat[] =
|
| + "https://qa-quirksserver-pa.sandbox.googleapis.com/v2/display/%08" PRIx64
|
| + "/clients/chromeos/M%d";
|
| +
|
| +// How often we query Quirks Server.
|
| +const int kDaysBetweenServerChecks = 30;
|
| +
|
| +// Sleep between Quirks retries (used multiplied by squared retry number).
|
| +const unsigned kRetrySleepSeconds = 10;
|
| +
|
| +// Retry is infinite with increasing intervals. When calculated delay becomes
|
| +// longer than maximum (kMaxRetrySleepSeconds) it is set to the maximum.
|
| +const double kMaxRetrySleepSeconds = 6 * 3600; // 6 hours
|
| +
|
| +// TODO!!! We're still sorting out the details of which key to use. This code
|
| +// is just here temporarily replacing the currently working api key.
|
| +std::string api_key_quirks() {
|
| + return std::string();
|
| +}
|
| +
|
| +std::string id2str(int64_t product_id) {
|
| + return base::StringPrintf("%08" PRIx64, product_id);
|
| +}
|
| +
|
| +// Check command line switch; the Quirks Client is current disabled by default.
|
| +bool QuirksClientEnabled() {
|
| + return base::CommandLine::ForCurrentProcess()->HasSwitch(
|
| + switches::kEnableDisplayQuirksClient);
|
| +}
|
| +
|
| +// Check if file exists, VLOG results.
|
| +bool CheckAndLogFile(const base::FilePath& path) {
|
| + const bool exists = base::PathExists(path);
|
| + VLOG(1) << (exists ? "File" : "No File") << " found at " << path.value();
|
| + // TODO(glevin): If file exists, do we want to implement a hash to verify that
|
| + // the file hasn't been corrupted or tampered with?
|
| + return exists;
|
| +}
|
| +
|
| +// Returns the path to the writable display profile directory.
|
| +// On chrome device, returns /var/cache/display_profiles.
|
| +// On Linux desktop, returns {DIR_USER_DATA}/display_profiles.
|
| +// Either directory must be created beforehand.
|
| +static base::FilePath GetDisplayProfileDirectory() {
|
| + base::FilePath directory;
|
| + if (base::SysInfo::IsRunningOnChromeOS()) {
|
| + PathService::Get(chromeos::DIR_DEVICE_DISPLAY_PROFILES, &directory);
|
| + } else {
|
| + // TODO!!! Fix this once this is moved to somewhere that we can include
|
| + // /chrome/ for |chrome::DIR_USER_DATA|.
|
| + const int DIR_USER_DATA_FAKE = 1002;
|
| + PathService::Get(DIR_USER_DATA_FAKE, &directory);
|
| +// PathService::Get(chrome::DIR_USER_DATA, &directory);
|
| + directory = directory.Append("display_profiles");
|
| + }
|
| + return directory;
|
| +}
|
| +
|
| +// Initializes QuirksClient object on the browser thread, where most of its work
|
| +// needs to be done.
|
| +void RunClientOnBrowserThread(
|
| + int64_t product_id,
|
| + QuirksClient::DownloadFinishedCallback* on_download_finished) {
|
| + if (!g_browser_delegate || !g_browser_delegate->Validate()) {
|
| + VLOG(1) << "Browser delegate not initialized; can't start Quirks Client.";
|
| + return;
|
| + }
|
| +
|
| + const base::Time last_check =
|
| + g_browser_delegate->GetLastServerCheck(product_id);
|
| + const base::TimeDelta time_since = base::Time::Now() - last_check;
|
| +
|
| + // If we haven't checked Quirks Server in the last 30 days, create a
|
| + // QuirksClient to check Quirks Server for icc file or updates.
|
| + if (time_since > base::TimeDelta::FromDays(kDaysBetweenServerChecks)) {
|
| + // TODO(glevin): Do we want to keep a pointer to this, or just let it roam
|
| + // free and delete itself when it's done?
|
| + /* Might need to handle interaction with delegate more intelligently. If
|
| + * offline, the client might persist, and keep trying. If it tries during
|
| + * shutdown, while the delegate is invalidating, it might cause trouble. */
|
| + QuirksClient* quirks_client =
|
| + new QuirksClient(product_id, on_download_finished);
|
| + quirks_client->Start();
|
| + } else {
|
| + VLOG(2) << time_since.InDays()
|
| + << " days since last Quirks Server check for display "
|
| + << id2str(product_id);
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// QuirksClient
|
| +
|
| +QuirksClient::QuirksClient(int64_t product_id,
|
| + DownloadFinishedCallback* on_download_finished)
|
| + : product_id_(product_id),
|
| + on_download_finished_(on_download_finished),
|
| + icc_path_(GetDisplayProfileDirectory().Append(id2str(product_id) +
|
| + ".icc")),
|
| + request_reason_(FIRST),
|
| + retries_(0),
|
| + retry_delay_(base::TimeDelta::FromSeconds(kRetrySleepSeconds)),
|
| + weak_ptr_factory_(this) {}
|
| +
|
| +QuirksClient::~QuirksClient() {}
|
| +
|
| +// static
|
| +base::FilePath QuirksClient::RequestIccProfilePath(
|
| + int64_t product_id,
|
| + DownloadFinishedCallback* on_download_finished) {
|
| + std::string file_name = id2str(product_id) + ".icc";
|
| +
|
| + // First, look for icc file in old read-only location. If there, we don't use
|
| + // the Quirks server.
|
| + // TODO(glevin): Awaiting final decision on how to handle these files.
|
| + base::FilePath path;
|
| + CHECK(
|
| + PathService::Get(chromeos::DIR_DEVICE_COLOR_CALIBRATION_PROFILES, &path));
|
| + path = path.Append(file_name);
|
| + if (CheckAndLogFile(path))
|
| + return path;
|
| +
|
| + // If Quirks Client is disabled, no other icc file is available.
|
| + if (!QuirksClientEnabled()) {
|
| + VLOG(1) << "Quirks client disabled, no built-in icc file available.";
|
| + return base::FilePath();
|
| + }
|
| +
|
| + // Check if QuirksClient has already downloaded icc file from server.
|
| + path = GetDisplayProfileDirectory().Append(file_name);
|
| + if (CheckAndLogFile(path))
|
| + return path;
|
| +
|
| + // TODO(glevin): Eventually we'll want to check the server for updates to
|
| + // files, so we'll still want to get down here even if we find the icc file.
|
| +
|
| + g_browser_delegate->RunClient(product_id, on_download_finished);
|
| +
|
| + return path;
|
| +}
|
| +
|
| +void QuirksClient::Start() {
|
| + // DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
| +
|
| + // URL of icc file on Quirks Server.
|
| + // TODO(glevin): Replace |44| with actual version. This is fine for now, as
|
| + // the Quirks Server is not currently using this value.
|
| + std::string url = base::StringPrintf(kQuirksUrlFormat, product_id_, 44);
|
| +
|
| + VLOG(2) << "Preparing to download\n " << url << "\nto file "
|
| + << icc_path_.value();
|
| +
|
| + url += "?key=";
|
| + url += api_key_quirks();
|
| +
|
| + url_fetcher_ = net::URLFetcher::Create(GURL(url), net::URLFetcher::GET, this);
|
| + url_fetcher_->SetRequestContext(g_browser_delegate->url_context_getter());
|
| + url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
|
| + net::LOAD_DISABLE_CACHE |
|
| + net::LOAD_DO_NOT_SAVE_COOKIES |
|
| + net::LOAD_DO_NOT_SEND_COOKIES |
|
| + net::LOAD_DO_NOT_SEND_AUTH_DATA);
|
| + url_fetcher_->Start();
|
| +}
|
| +
|
| +void QuirksClient::OnURLFetchComplete(const net::URLFetcher* source) {
|
| + // DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
| + DCHECK_EQ(url_fetcher_.get(), source);
|
| +
|
| + const net::URLRequestStatus status = source->GetStatus();
|
| + const int response_code = source->GetResponseCode();
|
| + const bool server_error =
|
| + !status.is_success() ||
|
| + (response_code >= net::HTTP_INTERNAL_SERVER_ERROR &&
|
| + response_code < (net::HTTP_INTERNAL_SERVER_ERROR + 100));
|
| +
|
| + VLOG(2) << "QuirksClient::OnURLFetchComplete():"
|
| + << " status=" << status.status()
|
| + << ", response_code=" << response_code
|
| + << ", server_error=" << server_error;
|
| +
|
| + g_browser_delegate->RecordReasonUmaStat(request_reason_);
|
| +
|
| + if (response_code == net::HTTP_NOT_FOUND) {
|
| + VLOG(1) << id2str(product_id_) << ".icc not found on Quirks server.";
|
| + g_browser_delegate->RecordFileFoundUmaStat(false);
|
| + Shutdown();
|
| + return;
|
| + }
|
| +
|
| + if (server_error) {
|
| + url_fetcher_.reset();
|
| + Retry();
|
| + return;
|
| + }
|
| +
|
| + g_browser_delegate->RecordFileFoundUmaStat(true);
|
| + std::string response;
|
| + url_fetcher_->GetResponseAsString(&response);
|
| + VLOG(2) << "Quirks server response:\n" << response;
|
| +
|
| + // Parse response data and write to file on io thread.
|
| + std::string data;
|
| + if (ParseResult(response, &data)) {
|
| + base::PostTaskAndReplyWithResult(
|
| + g_browser_delegate->blocking_pool(), FROM_HERE,
|
| + base::Bind(&WriteIccFile, icc_path_, data),
|
| + base::Bind(&QuirksClient::OnWriteIccFileFinished,
|
| + weak_ptr_factory_.GetWeakPtr()));
|
| + }
|
| +}
|
| +
|
| +void QuirksClient::Shutdown() {
|
| + g_browser_delegate->SetLastServerCheck(product_id_, base::Time::Now());
|
| + g_browser_delegate->message_loop_ui()->DeleteSoon(FROM_HERE, this);
|
| +}
|
| +
|
| +void QuirksClient::Retry() {
|
| + // DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
| + ++retries_;
|
| +
|
| + const double delay_seconds =
|
| + std::min(kMaxRetrySleepSeconds, static_cast<double>(retries_) * retries_ *
|
| + retry_delay_.InSecondsF());
|
| + const base::TimeDelta delay = base::TimeDelta::FromSecondsD(delay_seconds);
|
| +
|
| + VLOG(1) << "Schedule next Quirks download attempt in " << delay.InSecondsF()
|
| + << " seconds (retry = " << retries_ << ").";
|
| + retry_current_delay_ = delay;
|
| + request_scheduled_.Start(FROM_HERE, delay, this, &QuirksClient::Start);
|
| +}
|
| +
|
| +// static
|
| +bool QuirksClient::WriteIccFile(const base::FilePath file_path,
|
| + const std::string& data) {
|
| + int bytes_written = base::WriteFile(file_path, data.data(), data.length());
|
| + if (bytes_written == -1)
|
| + VLOG(1) << "Failed to write: " << file_path.value() << ", err = " << errno;
|
| + else
|
| + VLOG(1) << bytes_written << "bytes written to: " << file_path.value();
|
| +
|
| + return (bytes_written != -1);
|
| +}
|
| +
|
| +void QuirksClient::OnWriteIccFileFinished(bool success) {
|
| + if (success) {
|
| + g_browser_delegate->SetLastServerCheck(product_id_, base::Time::Now());
|
| + if (on_download_finished_)
|
| + on_download_finished_->Run(icc_path_);
|
| + }
|
| + // If we got data from server but couldn't write it, that's not likely to
|
| + // change this session, so give up.
|
| + Shutdown();
|
| +}
|
| +
|
| +bool QuirksClient::ParseResult(const std::string& result, std::string* data) {
|
| + std::string data64;
|
| + const base::DictionaryValue* dict;
|
| + scoped_ptr<base::Value> json = base::JSONReader::Read(result);
|
| + if (!json || !json->GetAsDictionary(&dict) ||
|
| + !dict->GetString("icc", &data64)) {
|
| + VLOG(1) << "Failed to parse JSON icc data";
|
| + return false;
|
| + }
|
| +
|
| + if (!base::Base64Decode(data64, data)) {
|
| + VLOG(1) << "Failed to decode Base64 icc data";
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +////////////////////////////////////////////////////////////////////////////////
|
| +// QuirksClientBrowserDelegate
|
| +
|
| +// static
|
| +void QuirksClientBrowserDelegate::Initialize(
|
| + base::MessageLoopForUI* message_loop_ui,
|
| + base::SequencedWorkerPool* blocking_pool,
|
| + PrefService* local_state,
|
| + net::URLRequestContextGetter* url_context_getter) {
|
| + if (!QuirksClientEnabled())
|
| + return;
|
| + g_browser_delegate = new QuirksClientBrowserDelegate(
|
| + message_loop_ui, blocking_pool, local_state, url_context_getter);
|
| +}
|
| +
|
| +// static
|
| +void QuirksClientBrowserDelegate::Shutdown() {
|
| + delete g_browser_delegate;
|
| + g_browser_delegate = nullptr;
|
| +}
|
| +
|
| +// static
|
| +void QuirksClientBrowserDelegate::RegisterPrefs(PrefRegistrySimple* registry) {
|
| + registry->RegisterDictionaryPref(prefs::kQuirksClientLastServerCheck);
|
| +}
|
| +
|
| +void QuirksClientBrowserDelegate::RunClient(
|
| + int64_t product_id,
|
| + QuirksClient::DownloadFinishedCallback* on_download_finished) {
|
| + message_loop_ui_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&RunClientOnBrowserThread, product_id, on_download_finished));
|
| +}
|
| +
|
| +base::Time QuirksClientBrowserDelegate::GetLastServerCheck(int64_t product_id) {
|
| + double last_check = 0.0;
|
| + const base::DictionaryValue* dict =
|
| + local_state_->GetDictionary(prefs::kQuirksClientLastServerCheck);
|
| + if (dict)
|
| + dict->GetDouble(id2str(product_id), &last_check);
|
| + // TODO!!! if (last_check == 0), set last_check = rand[-30,0] days. That is,
|
| + // stagger initial server call over 30 day interval, unless machine is
|
| + // < 1 month old.
|
| + return base::Time::FromDoubleT(last_check);
|
| +}
|
| +
|
| +void QuirksClientBrowserDelegate::SetLastServerCheck(int64_t product_id,
|
| + base::Time last_check) {
|
| + DictionaryPrefUpdate dict(local_state_, prefs::kQuirksClientLastServerCheck);
|
| + dict->SetDouble(id2str(product_id), last_check.ToDoubleT());
|
| +}
|
| +
|
| +// TODO(glevin): Add code to record UMA stats here. Also need to set
|
| +// request_reason_ in QuirksClient.
|
| +void QuirksClientBrowserDelegate::RecordReasonUmaStat(
|
| + QuirksClient::RequestReason reason) {}
|
| +
|
| +void QuirksClientBrowserDelegate::RecordFileFoundUmaStat(bool success) {}
|
| +
|
| +bool QuirksClientBrowserDelegate::Validate() {
|
| + return base::MessageLoopForUI::current()->is_running() &&
|
| + local_state_->CalledOnValidThread() &&
|
| + base::ThreadTaskRunnerHandle::IsSet();
|
| +}
|
| +
|
| +QuirksClientBrowserDelegate::QuirksClientBrowserDelegate(
|
| + base::MessageLoopForUI* message_loop_ui,
|
| + base::SequencedWorkerPool* blocking_pool,
|
| + PrefService* local_state,
|
| + net::URLRequestContextGetter* url_context_getter)
|
| + : message_loop_ui_(message_loop_ui),
|
| + blocking_pool_(blocking_pool),
|
| + local_state_(local_state),
|
| + url_context_getter_(url_context_getter) {}
|
| +
|
| +QuirksClientBrowserDelegate::~QuirksClientBrowserDelegate() {}
|
| +
|
| +} // namespace chromeos
|
|
|