Home | History | Annotate | Download | only in prototype
      1 // Copyright 2013 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 "cloud_print/gcp20/prototype/printer.h"
      6 
      7 #include <stdio.h>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/bind.h"
     12 #include "base/command_line.h"
     13 #include "base/file_util.h"
     14 #include "base/guid.h"
     15 #include "base/json/json_reader.h"
     16 #include "base/json/json_writer.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/stringprintf.h"
     19 #include "cloud_print/gcp20/prototype/command_line_reader.h"
     20 #include "cloud_print/gcp20/prototype/service_parameters.h"
     21 #include "cloud_print/gcp20/prototype/special_io.h"
     22 #include "net/base/net_util.h"
     23 #include "net/base/url_util.h"
     24 
     25 const char kPrinterStatePathDefault[] = "printer_state.json";
     26 
     27 namespace {
     28 
     29 const uint16 kHttpPortDefault = 10101;
     30 const uint32 kTtlDefault = 60*60;  // in seconds
     31 const int kXmppPingIntervalDefault = 5*60;  // in seconds
     32 
     33 const char kServiceType[] = "_privet._tcp.local";
     34 const char kServiceNamePrefixDefault[] = "first_gcp20_device";
     35 const char kServiceDomainNameDefault[] = "my-privet-device.local";
     36 
     37 const char kPrinterName[] = "Google GCP2.0 Prototype";
     38 const char kPrinterDescription[] = "Printer emulator";
     39 
     40 const char kUserConfirmationTitle[] = "Confirm registration: type 'y' if you "
     41                                       "agree and any other to discard\n";
     42 const int64 kUserConfirmationTimeout = 30;  // in seconds
     43 
     44 const uint32 kReconnectTimeout = 5;  // in seconds
     45 
     46 const double kTimeToNextAccessTokenUpdate = 0.8;  // relatively to living time.
     47 
     48 const char kCdd[] =
     49 "{\n"
     50 " 'version': '1.0',\n"
     51 "  'printer': {\n"
     52 "    'vendor_capability': [\n"
     53 "      {\n"
     54 "        'id': 'psk:MediaType',\n"
     55 "        'display_name': 'Media Type',\n"
     56 "        'type': 'SELECT',\n"
     57 "        'select_cap': {\n"
     58 "          'option': [\n"
     59 "            {\n"
     60 "              'value': 'psk:Plain',\n"
     61 "              'display_name': 'Plain Paper',\n"
     62 "              'is_default': true\n"
     63 "            },\n"
     64 "            {\n"
     65 "              'value': 'ns0000:Glossy',\n"
     66 "              'display_name': 'Glossy Photo',\n"
     67 "              'is_default': false\n"
     68 "            }\n"
     69 "          ]\n"
     70 "        }\n"
     71 "      }\n"
     72 "    ],\n"
     73 "    'reverse_order': { 'default': false }\n"
     74 "  }\n"
     75 "}\n";
     76 
     77 // Returns local IP address number of first interface found (except loopback).
     78 // Return value is empty if no interface found. Possible interfaces names are
     79 // "eth0", "wlan0" etc. If interface name is empty, function will return IP
     80 // address of first interface found.
     81 net::IPAddressNumber GetLocalIp(const std::string& interface_name,
     82                                 bool return_ipv6_number) {
     83   net::NetworkInterfaceList interfaces;
     84   bool success = net::GetNetworkList(&interfaces);
     85   DCHECK(success);
     86 
     87   size_t expected_address_size = return_ipv6_number ? net::kIPv6AddressSize
     88                                                     : net::kIPv4AddressSize;
     89 
     90   for (net::NetworkInterfaceList::iterator iter = interfaces.begin();
     91        iter != interfaces.end(); ++iter) {
     92     if (iter->address.size() == expected_address_size &&
     93         (interface_name.empty() || interface_name == iter->name)) {
     94       LOG(INFO) << net::IPAddressToString(iter->address);
     95       return iter->address;
     96     }
     97   }
     98 
     99   return net::IPAddressNumber();
    100 }
    101 
    102 scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() {
    103   return base::MessageLoop::current()->message_loop_proxy();
    104 }
    105 
    106 }  // namespace
    107 
    108 using cloud_print_response_parser::Job;
    109 
    110 Printer::RegistrationInfo::RegistrationInfo()
    111     : state(DEV_REG_UNREGISTERED),
    112       confirmation_state(CONFIRMATION_PENDING) {
    113 }
    114 
    115 Printer::RegistrationInfo::~RegistrationInfo() {
    116 }
    117 
    118 Printer::Printer()
    119     : http_server_(this),
    120       connection_state_(OFFLINE),
    121       on_idle_posted_(false),
    122       pending_local_settings_check_(false),
    123       pending_print_jobs_check_(false) {
    124 }
    125 
    126 Printer::~Printer() {
    127   Stop();
    128 }
    129 
    130 bool Printer::Start() {
    131   if (IsRunning())
    132     return true;
    133 
    134   // TODO(maksymb): Add switch for command line to control interface name.
    135   net::IPAddressNumber ip = GetLocalIp("", false);
    136   if (ip.empty()) {
    137     LOG(ERROR) << "No local IP found. Cannot start printer.";
    138     return false;
    139   }
    140   VLOG(1) << "Local address: " << net::IPAddressToString(ip);
    141 
    142   uint16 port = command_line_reader::ReadHttpPort(kHttpPortDefault);
    143 
    144   // Starting HTTP server.
    145   if (!http_server_.Start(port))
    146     return false;
    147 
    148   if (!LoadFromFile())
    149     reg_info_ = RegistrationInfo();
    150 
    151   // Starting DNS-SD server.
    152   std::string service_name_prefix =
    153       command_line_reader::ReadServiceNamePrefix(kServiceNamePrefixDefault);
    154   std::string service_domain_name =
    155       command_line_reader::ReadDomainName(kServiceDomainNameDefault);
    156   if (!dns_server_.Start(
    157       ServiceParameters(kServiceType, service_name_prefix, service_domain_name,
    158                         ip, port),
    159       command_line_reader::ReadTtl(kTtlDefault),
    160       CreateTxt())) {
    161     http_server_.Shutdown();
    162     return false;
    163   }
    164 
    165   print_job_handler_.reset(new PrintJobHandler);
    166   xtoken_ = XPrivetToken();
    167   starttime_ = base::Time::Now();
    168 
    169   TryConnect();
    170   return true;
    171 }
    172 
    173 bool Printer::IsRunning() const {
    174   return print_job_handler_;
    175 }
    176 
    177 void Printer::Stop() {
    178   dns_server_.Shutdown();
    179   http_server_.Shutdown();
    180   requester_.reset();
    181   print_job_handler_.reset();
    182   xmpp_listener_.reset();
    183 }
    184 
    185 void Printer::OnAuthError() {
    186   LOG(ERROR) << "Auth error occurred";
    187   access_token_update_ = base::Time::Now();
    188   ChangeState(OFFLINE);
    189   // TODO(maksymb): Implement *instant* updating of access_token.
    190 }
    191 
    192 std::string Printer::GetAccessToken() {
    193   return access_token_;
    194 }
    195 
    196 PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationStart(
    197     const std::string& user) {
    198   PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
    199   if (status != PrivetHttpServer::REG_ERROR_OK)
    200     return status;
    201 
    202   if (reg_info_.state != RegistrationInfo::DEV_REG_UNREGISTERED)
    203     return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
    204 
    205   reg_info_ = RegistrationInfo();
    206   reg_info_.user = user;
    207   reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_STARTED;
    208 
    209   if (CommandLine::ForCurrentProcess()->HasSwitch("disable-confirmation")) {
    210     reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_CONFIRMED;
    211     LOG(INFO) << "Registration confirmed by default.";
    212   } else {
    213     printf("%s", kUserConfirmationTitle);
    214     base::Time valid_until = base::Time::Now() +
    215         base::TimeDelta::FromSeconds(kUserConfirmationTimeout);
    216     base::MessageLoop::current()->PostTask(
    217         FROM_HERE,
    218         base::Bind(&Printer::WaitUserConfirmation, AsWeakPtr(), valid_until));
    219   }
    220 
    221   requester_->StartRegistration(GenerateProxyId(), kPrinterName, user, kCdd);
    222 
    223   return PrivetHttpServer::REG_ERROR_OK;
    224 }
    225 
    226 PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationGetClaimToken(
    227     const std::string& user,
    228     std::string* token,
    229     std::string* claim_url) {
    230   PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
    231   if (status != PrivetHttpServer::REG_ERROR_OK)
    232     return status;
    233 
    234   // Check if |action=start| was called, but |action=complete| wasn't.
    235   if (reg_info_.state != RegistrationInfo::DEV_REG_REGISTRATION_STARTED &&
    236       reg_info_.state !=
    237           RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY)
    238     return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
    239 
    240   // If |action=getClaimToken| is valid in this state (was checked above) then
    241   // check confirmation status.
    242   if (reg_info_.confirmation_state != RegistrationInfo::CONFIRMATION_CONFIRMED)
    243     return ConfirmationToRegistrationError(reg_info_.confirmation_state);
    244 
    245   // If reply wasn't received yet, reply with |pending_user_action| error.
    246   if (reg_info_.state == RegistrationInfo::DEV_REG_REGISTRATION_STARTED)
    247     return PrivetHttpServer::REG_ERROR_PENDING_USER_ACTION;
    248 
    249   DCHECK_EQ(reg_info_.state,
    250             RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY);
    251   DCHECK_EQ(reg_info_.confirmation_state,
    252             RegistrationInfo::CONFIRMATION_CONFIRMED);
    253 
    254   *token = reg_info_.registration_token;
    255   *claim_url = reg_info_.complete_invite_url;
    256   return PrivetHttpServer::REG_ERROR_OK;
    257 }
    258 
    259 PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationComplete(
    260     const std::string& user,
    261     std::string* device_id) {
    262   PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
    263   if (status != PrivetHttpServer::REG_ERROR_OK)
    264     return status;
    265 
    266   if (reg_info_.state !=
    267       RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY) {
    268     return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
    269   }
    270 
    271   if (reg_info_.confirmation_state != RegistrationInfo::CONFIRMATION_CONFIRMED)
    272     return ConfirmationToRegistrationError(reg_info_.confirmation_state);
    273 
    274   reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_COMPLETING;
    275   requester_->CompleteRegistration();
    276   *device_id = reg_info_.device_id;
    277 
    278   return PrivetHttpServer::REG_ERROR_OK;
    279 }
    280 
    281 PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationCancel(
    282     const std::string& user) {
    283   PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
    284   if (status != PrivetHttpServer::REG_ERROR_OK &&
    285       status != PrivetHttpServer::REG_ERROR_SERVER_ERROR) {
    286     return status;
    287   }
    288 
    289   if (reg_info_.state == RegistrationInfo::DEV_REG_UNREGISTERED)
    290     return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
    291 
    292   reg_info_ = RegistrationInfo();
    293   requester_.reset(new CloudPrintRequester(GetTaskRunner(), this));
    294 
    295   return PrivetHttpServer::REG_ERROR_OK;
    296 }
    297 
    298 void Printer::GetRegistrationServerError(std::string* description) {
    299   DCHECK_EQ(reg_info_.state, RegistrationInfo::DEV_REG_REGISTRATION_ERROR) <<
    300       "Method shouldn't be called when not needed.";
    301 
    302   *description = reg_info_.error_description;
    303 }
    304 
    305 void Printer::CreateInfo(PrivetHttpServer::DeviceInfo* info) {
    306   // TODO(maksymb): Replace "text" with constants.
    307 
    308   *info = PrivetHttpServer::DeviceInfo();
    309   info->version = "1.0";
    310   info->name = kPrinterName;
    311   info->description = kPrinterDescription;
    312   info->url = kCloudPrintUrl;
    313   info->id = reg_info_.device_id;
    314   info->device_state = "idle";
    315   info->connection_state = ConnectionStateToString(connection_state_);
    316   info->manufacturer = "Google";
    317   info->model = "Prototype";
    318   info->serial_number = "2.3.5.7.13.17.19.31.61.89.107.127.521.607.1279.2203";
    319   info->firmware = "3.7.31.127.8191.131071.524287.2147483647";
    320   info->uptime = static_cast<int>((base::Time::Now() - starttime_).InSeconds());
    321 
    322   info->x_privet_token = xtoken_.GenerateXToken();
    323 
    324   if (reg_info_.state == RegistrationInfo::DEV_REG_UNREGISTERED)
    325     info->api.push_back("/privet/register");
    326 
    327   info->type.push_back("printer");
    328 }
    329 
    330 bool Printer::IsRegistered() const {
    331   return reg_info_.state == RegistrationInfo::DEV_REG_REGISTERED;
    332 }
    333 
    334 bool Printer::CheckXPrivetTokenHeader(const std::string& token) const {
    335   return xtoken_.CheckValidXToken(token);
    336 }
    337 
    338 void Printer::OnRegistrationStartResponseParsed(
    339     const std::string& registration_token,
    340     const std::string& complete_invite_url,
    341     const std::string& device_id) {
    342   reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY;
    343   reg_info_.device_id = device_id;
    344   reg_info_.registration_token = registration_token;
    345   reg_info_.complete_invite_url = complete_invite_url;
    346 }
    347 
    348 void Printer::OnGetAuthCodeResponseParsed(const std::string& refresh_token,
    349                                           const std::string& access_token,
    350                                           int access_token_expires_in_seconds) {
    351   reg_info_.state = RegistrationInfo::DEV_REG_REGISTERED;
    352   reg_info_.refresh_token = refresh_token;
    353   RememberAccessToken(access_token, access_token_expires_in_seconds);
    354   TryConnect();
    355 }
    356 
    357 void Printer::OnAccesstokenReceviced(const std::string& access_token,
    358                                      int expires_in_seconds) {
    359   VLOG(3) << "Function: " << __FUNCTION__;
    360   RememberAccessToken(access_token, expires_in_seconds);
    361   switch (connection_state_) {
    362     case ONLINE:
    363       PostOnIdle();
    364       break;
    365 
    366     case CONNECTING:
    367       TryConnect();
    368       break;
    369 
    370     default:
    371       NOTREACHED();
    372   }
    373 }
    374 
    375 void Printer::OnXmppJidReceived(const std::string& xmpp_jid) {
    376   reg_info_.xmpp_jid = xmpp_jid;
    377 }
    378 
    379 void Printer::OnRegistrationError(const std::string& description) {
    380   LOG(ERROR) << "server_error: " << description;
    381 
    382   // TODO(maksymb): Implement waiting after error and timeout of registration.
    383   reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_ERROR;
    384   reg_info_.error_description = description;
    385 }
    386 
    387 void Printer::OnNetworkError() {
    388   VLOG(3) << "Function: " << __FUNCTION__;
    389   ChangeState(OFFLINE);
    390 }
    391 
    392 void Printer::OnServerError(const std::string& description) {
    393   VLOG(3) << "Function: " << __FUNCTION__;
    394   LOG(ERROR) << "Server error: " << description;
    395 
    396   ChangeState(OFFLINE);
    397 }
    398 
    399 void Printer::OnPrintJobsAvailable(const std::vector<Job>& jobs) {
    400   VLOG(3) << "Function: " << __FUNCTION__;
    401 
    402   LOG(INFO) << "Available printjobs: " << jobs.size();
    403 
    404   if (jobs.empty()) {
    405     pending_print_jobs_check_ = false;
    406     PostOnIdle();
    407     return;
    408   }
    409 
    410   LOG(INFO) << "Downloading printjob.";
    411   requester_->RequestPrintJob(jobs[0]);
    412   return;
    413 }
    414 
    415 void Printer::OnPrintJobDownloaded(const Job& job) {
    416   VLOG(3) << "Function: " << __FUNCTION__;
    417   print_job_handler_->SavePrintJob(
    418       job.file,
    419       job.ticket,
    420       base::StringPrintf("%s.%s", job.create_time.c_str(), job.job_id.c_str()),
    421       job.title);
    422   requester_->SendPrintJobDone(job.job_id);
    423 }
    424 
    425 void Printer::OnPrintJobDone() {
    426   VLOG(3) << "Function: " << __FUNCTION__;
    427   PostOnIdle();
    428 }
    429 
    430 void Printer::OnXmppConnected() {
    431   pending_local_settings_check_ = true;
    432   pending_print_jobs_check_ = true;
    433   ChangeState(ONLINE);
    434   PostOnIdle();
    435 }
    436 
    437 void Printer::OnXmppAuthError() {
    438   OnAuthError();
    439 }
    440 
    441 void Printer::OnXmppNetworkError() {
    442   ChangeState(OFFLINE);
    443 }
    444 
    445 void Printer::OnXmppNewPrintJob(const std::string& device_id) {
    446   DCHECK_EQ(reg_info_.device_id, device_id) << "Data should contain printer_id";
    447   pending_print_jobs_check_ = true;
    448 }
    449 
    450 void Printer::OnXmppNewLocalSettings(const std::string& device_id) {
    451   DCHECK_EQ(reg_info_.device_id, device_id) << "Data should contain printer_id";
    452   NOTIMPLEMENTED();
    453 }
    454 
    455 void Printer::OnXmppDeleteNotification(const std::string& device_id) {
    456   DCHECK_EQ(reg_info_.device_id, device_id) << "Data should contain printer_id";
    457   NOTIMPLEMENTED();
    458 }
    459 
    460 void Printer::TryConnect() {
    461   VLOG(3) << "Function: " << __FUNCTION__;
    462 
    463   ChangeState(CONNECTING);
    464   if (!requester_)
    465     requester_.reset(new CloudPrintRequester(GetTaskRunner(), this));
    466 
    467   if (IsRegistered()) {
    468     if (access_token_update_ < base::Time::Now()) {
    469       requester_->UpdateAccesstoken(reg_info_.refresh_token);
    470     } else {
    471       ConnectXmpp();
    472     }
    473   } else {
    474     // TODO(maksymb): Ping google.com to check connection state.
    475     ChangeState(ONLINE);
    476   }
    477 }
    478 
    479 void Printer::ConnectXmpp() {
    480   xmpp_listener_.reset(
    481       new CloudPrintXmppListener(reg_info_.xmpp_jid, kXmppPingIntervalDefault,
    482                                  GetTaskRunner(), this));
    483   xmpp_listener_->Connect(access_token_);
    484 }
    485 
    486 void Printer::OnIdle() {
    487   DCHECK(IsRegistered());
    488   DCHECK(on_idle_posted_) << "Instant call is not allowed";
    489   on_idle_posted_ = false;
    490 
    491   if (connection_state_ != ONLINE)
    492     return;
    493 
    494   if (access_token_update_ < base::Time::Now()) {
    495     requester_->UpdateAccesstoken(reg_info_.refresh_token);
    496     return;
    497   }
    498 
    499   // TODO(maksymb): Check if privet-accesstoken was requested.
    500 
    501   // TODO(maksymb): Check if local-printing was requested.
    502 
    503   if (pending_local_settings_check_) {
    504     GetLocalSettings();
    505     return;
    506   }
    507 
    508   if (pending_print_jobs_check_) {
    509     FetchPrintJobs();
    510     return;
    511   }
    512 
    513   base::MessageLoop::current()->PostDelayedTask(
    514         FROM_HERE,
    515         base::Bind(&Printer::PostOnIdle, AsWeakPtr()),
    516         base::TimeDelta::FromMilliseconds(1000));
    517 }
    518 
    519 void Printer::GetLocalSettings() {
    520   DCHECK(IsRegistered());
    521 
    522   pending_local_settings_check_ = false;
    523   PostOnIdle();
    524 }
    525 
    526 void Printer::FetchPrintJobs() {
    527   VLOG(3) << "Function: " << __FUNCTION__;
    528 
    529   DCHECK(IsRegistered());
    530   requester_->FetchPrintJobs(reg_info_.device_id);
    531 }
    532 
    533 void Printer::RememberAccessToken(const std::string& access_token,
    534                                   int expires_in_seconds) {
    535   using base::Time;
    536   using base::TimeDelta;
    537   access_token_ = access_token;
    538   int64 time_to_update = static_cast<int64>(expires_in_seconds *
    539                                             kTimeToNextAccessTokenUpdate);
    540   access_token_update_ = Time::Now() + TimeDelta::FromSeconds(time_to_update);
    541   VLOG(1) << "Current access_token: " << access_token;
    542   SaveToFile();
    543 }
    544 
    545 PrivetHttpServer::RegistrationErrorStatus Printer::CheckCommonRegErrors(
    546     const std::string& user) const {
    547   DCHECK(!IsRegistered());
    548 
    549   if (reg_info_.state != RegistrationInfo::DEV_REG_UNREGISTERED &&
    550       user != reg_info_.user) {
    551     return PrivetHttpServer::REG_ERROR_DEVICE_BUSY;
    552   }
    553 
    554   if (reg_info_.state == RegistrationInfo::DEV_REG_REGISTRATION_ERROR)
    555     return PrivetHttpServer::REG_ERROR_SERVER_ERROR;
    556 
    557   return PrivetHttpServer::REG_ERROR_OK;
    558 }
    559 
    560 void Printer::WaitUserConfirmation(base::Time valid_until) {
    561   if (base::Time::Now() > valid_until) {
    562     reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_TIMEOUT;
    563     LOG(INFO) << "Confirmation timeout reached.";
    564     return;
    565   }
    566 
    567   if (_kbhit()) {
    568     int c = _getche();
    569     if (c == 'y' || c == 'Y') {
    570       reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_CONFIRMED;
    571       LOG(INFO) << "Registration confirmed by user.";
    572     } else {
    573       reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_DISCARDED;
    574       LOG(INFO) << "Registration discarded by user.";
    575     }
    576     return;
    577   }
    578 
    579   base::MessageLoop::current()->PostDelayedTask(
    580       FROM_HERE,
    581       base::Bind(&Printer::WaitUserConfirmation, AsWeakPtr(), valid_until),
    582       base::TimeDelta::FromMilliseconds(100));
    583 }
    584 
    585 std::string Printer::GenerateProxyId() const {
    586   return "{" + base::GenerateGUID() +"}";
    587 }
    588 
    589 std::vector<std::string> Printer::CreateTxt() const {
    590   std::vector<std::string> txt;
    591   txt.push_back("txtvers=1");
    592   txt.push_back("ty=" + std::string(kPrinterName));
    593   txt.push_back("note=" + std::string(kPrinterDescription));
    594   txt.push_back("url=" + std::string(kCloudPrintUrl));
    595   txt.push_back("type=printer");
    596   txt.push_back("id=" + reg_info_.device_id);
    597   txt.push_back("cs=" + ConnectionStateToString(connection_state_));
    598 
    599   return txt;
    600 }
    601 
    602 void Printer::SaveToFile() const {
    603   base::FilePath file_path;
    604   file_path = file_path.AppendASCII(
    605       command_line_reader::ReadStatePath(kPrinterStatePathDefault));
    606 
    607   base::DictionaryValue json;
    608   // TODO(maksymb): Get rid of in-place constants.
    609   if (IsRegistered()) {
    610     json.SetBoolean("registered", true);
    611     json.SetString("user", reg_info_.user);
    612     json.SetString("device_id", reg_info_.device_id);
    613     json.SetString("refresh_token", reg_info_.refresh_token);
    614     json.SetString("xmpp_jid", reg_info_.xmpp_jid);
    615     json.SetString("access_token", access_token_);
    616     json.SetInteger("access_token_update",
    617                     static_cast<int>(access_token_update_.ToTimeT()));
    618   } else {
    619     json.SetBoolean("registered", false);
    620   }
    621 
    622   std::string json_str;
    623   base::JSONWriter::WriteWithOptions(&json,
    624                                      base::JSONWriter::OPTIONS_PRETTY_PRINT,
    625                                      &json_str);
    626   if (!file_util::WriteFile(file_path, json_str.data(),
    627                             static_cast<int>(json_str.size()))) {
    628     LOG(ERROR) << "Cannot write state.";
    629   }
    630   LOG(INFO) << "State written to file.";
    631 }
    632 
    633 bool Printer::LoadFromFile() {
    634   base::FilePath file_path;
    635   file_path = file_path.AppendASCII(
    636       command_line_reader::ReadStatePath(kPrinterStatePathDefault));
    637   if (!base::PathExists(file_path)) {
    638     LOG(INFO) << "Registration info is not found. Printer is unregistered.";
    639     return false;
    640   }
    641 
    642   LOG(INFO) << "Loading registration info from file.";
    643   std::string json_str;
    644   if (!file_util::ReadFileToString(file_path, &json_str)) {
    645     LOG(ERROR) << "Cannot open file.";
    646     return false;
    647   }
    648 
    649   scoped_ptr<base::Value> json_val(base::JSONReader::Read(json_str));
    650   base::DictionaryValue* json = NULL;
    651   if (!json_val || !json_val->GetAsDictionary(&json)) {
    652     LOG(ERROR) << "Cannot read JSON dictionary from file.";
    653     return false;
    654   }
    655 
    656   bool registered = false;
    657   if (!json->GetBoolean("registered", &registered)) {
    658     LOG(ERROR) << "Cannot parse |registered| state.";
    659     return false;
    660   }
    661 
    662   if (!registered) {
    663     reg_info_ = RegistrationInfo();
    664     return true;
    665   }
    666 
    667   std::string user;
    668   if (!json->GetString("user", &user)) {
    669     LOG(ERROR) << "Cannot parse |user|.";
    670     return false;
    671   }
    672 
    673   std::string device_id;
    674   if (!json->GetString("device_id", &device_id)) {
    675     LOG(ERROR) << "Cannot parse |device_id|.";
    676     return false;
    677   }
    678 
    679   std::string refresh_token;
    680   if (!json->GetString("refresh_token", &refresh_token)) {
    681     LOG(ERROR) << "Cannot parse |refresh_token|.";
    682     return false;
    683   }
    684 
    685   std::string xmpp_jid;
    686   if (!json->GetString("xmpp_jid", &xmpp_jid)) {
    687     LOG(ERROR) << "Cannot parse |xmpp_jid|.";
    688     return false;
    689   }
    690 
    691   std::string access_token;
    692   if (!json->GetString("access_token", &access_token)) {
    693     LOG(ERROR) << "Cannot parse |access_token|.";
    694     return false;
    695   }
    696 
    697   int access_token_update;
    698   if (!json->GetInteger("access_token_update", &access_token_update)) {
    699     LOG(ERROR) << "Cannot parse |access_token_update|.";
    700     return false;
    701   }
    702 
    703   reg_info_ = RegistrationInfo();
    704   reg_info_.state = RegistrationInfo::DEV_REG_REGISTERED;
    705   reg_info_.user = user;
    706   reg_info_.device_id = device_id;
    707   reg_info_.refresh_token = refresh_token;
    708   reg_info_.xmpp_jid = xmpp_jid;
    709   using base::Time;
    710   access_token_ = access_token;
    711   access_token_update_ = Time::FromTimeT(access_token_update);
    712 
    713   return true;
    714 }
    715 
    716 void Printer::PostOnIdle() {
    717   VLOG(3) << "Function: " << __FUNCTION__;
    718   DCHECK(!on_idle_posted_) << "Only one instance can be posted.";
    719   on_idle_posted_ = true;
    720 
    721   base::MessageLoop::current()->PostTask(
    722       FROM_HERE,
    723       base::Bind(&Printer::OnIdle, AsWeakPtr()));
    724 }
    725 
    726 PrivetHttpServer::RegistrationErrorStatus
    727     Printer::ConfirmationToRegistrationError(
    728         RegistrationInfo::ConfirmationState state) {
    729   switch (state) {
    730     case RegistrationInfo::CONFIRMATION_PENDING:
    731       return PrivetHttpServer::REG_ERROR_PENDING_USER_ACTION;
    732     case RegistrationInfo::CONFIRMATION_DISCARDED:
    733       return PrivetHttpServer::REG_ERROR_USER_CANCEL;
    734     case RegistrationInfo::CONFIRMATION_CONFIRMED:
    735       NOTREACHED();
    736       return PrivetHttpServer::REG_ERROR_OK;
    737     case RegistrationInfo::CONFIRMATION_TIMEOUT:
    738       return PrivetHttpServer::REG_ERROR_CONFIRMATION_TIMEOUT;
    739     default:
    740       NOTREACHED();
    741       return PrivetHttpServer::REG_ERROR_OK;
    742   }
    743 }
    744 
    745 std::string Printer::ConnectionStateToString(ConnectionState state) const {
    746   switch (state) {
    747     case OFFLINE:
    748       return "offline";
    749     case ONLINE:
    750       return "online";
    751     case CONNECTING:
    752       return "connecting";
    753     case NOT_CONFIGURED:
    754       return "not-configured";
    755 
    756     default:
    757       NOTREACHED();
    758       return "";
    759   }
    760 }
    761 
    762 bool Printer::ChangeState(ConnectionState new_state) {
    763   if (connection_state_ == new_state)
    764     return false;
    765 
    766   connection_state_ = new_state;
    767   LOG(INFO) << base::StringPrintf(
    768       "Printer is now %s (%s)",
    769       ConnectionStateToString(connection_state_).c_str(),
    770       IsRegistered() ? "registered" : "unregistered");
    771 
    772   dns_server_.UpdateMetadata(CreateTxt());
    773 
    774   switch (connection_state_) {
    775     case CONNECTING:
    776       break;
    777 
    778     case ONLINE:
    779       break;
    780 
    781     case OFFLINE:
    782       requester_.reset();
    783       xmpp_listener_.reset();
    784       base::MessageLoop::current()->PostDelayedTask(
    785           FROM_HERE,
    786           base::Bind(&Printer::TryConnect, AsWeakPtr()),
    787           base::TimeDelta::FromSeconds(kReconnectTimeout));
    788 
    789     case NOT_CONFIGURED:
    790       break;
    791 
    792     default:
    793       NOTREACHED();
    794   }
    795 
    796   return true;
    797 }
    798 
    799