blob: 4583aa70b4ebfc3ff561f71ac6290afda17b4fe5 [file] [log] [blame]
// Copyright 2013 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 "cloud_print/gcp20/prototype/printer.h"
#include <stdio.h>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/guid.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "cloud_print/gcp20/prototype/command_line_reader.h"
#include "cloud_print/gcp20/prototype/service_parameters.h"
#include "cloud_print/gcp20/prototype/special_io.h"
#include "net/base/net_util.h"
#include "net/base/url_util.h"
const char kPrinterStatePathDefault[] = "printer_state.json";
namespace {
const uint16 kHttpPortDefault = 10101;
const uint32 kTtlDefault = 60*60; // in seconds
const int kXmppPingIntervalDefault = 5*60; // in seconds
const char kServiceType[] = "_privet._tcp.local";
const char kServiceNamePrefixDefault[] = "first_gcp20_device";
const char kServiceDomainNameDefault[] = "my-privet-device.local";
const char kPrinterName[] = "Google GCP2.0 Prototype";
const char kPrinterDescription[] = "Printer emulator";
const char kUserConfirmationTitle[] = "Confirm registration: type 'y' if you "
"agree and any other to discard\n";
const int64 kUserConfirmationTimeout = 30; // in seconds
const uint32 kReconnectTimeout = 5; // in seconds
const double kTimeToNextAccessTokenUpdate = 0.8; // relatively to living time.
const char kCdd[] =
"{\n"
" 'version': '1.0',\n"
" 'printer': {\n"
" 'vendor_capability': [\n"
" {\n"
" 'id': 'psk:MediaType',\n"
" 'display_name': 'Media Type',\n"
" 'type': 'SELECT',\n"
" 'select_cap': {\n"
" 'option': [\n"
" {\n"
" 'value': 'psk:Plain',\n"
" 'display_name': 'Plain Paper',\n"
" 'is_default': true\n"
" },\n"
" {\n"
" 'value': 'ns0000:Glossy',\n"
" 'display_name': 'Glossy Photo',\n"
" 'is_default': false\n"
" }\n"
" ]\n"
" }\n"
" }\n"
" ],\n"
" 'reverse_order': { 'default': false }\n"
" }\n"
"}\n";
// Returns local IP address number of first interface found (except loopback).
// Return value is empty if no interface found. Possible interfaces names are
// "eth0", "wlan0" etc. If interface name is empty, function will return IP
// address of first interface found.
net::IPAddressNumber GetLocalIp(const std::string& interface_name,
bool return_ipv6_number) {
net::NetworkInterfaceList interfaces;
bool success = net::GetNetworkList(&interfaces);
DCHECK(success);
size_t expected_address_size = return_ipv6_number ? net::kIPv6AddressSize
: net::kIPv4AddressSize;
for (net::NetworkInterfaceList::iterator iter = interfaces.begin();
iter != interfaces.end(); ++iter) {
if (iter->address.size() == expected_address_size &&
(interface_name.empty() || interface_name == iter->name)) {
LOG(INFO) << net::IPAddressToString(iter->address);
return iter->address;
}
}
return net::IPAddressNumber();
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() {
return base::MessageLoop::current()->message_loop_proxy();
}
} // namespace
using cloud_print_response_parser::Job;
Printer::RegistrationInfo::RegistrationInfo()
: state(DEV_REG_UNREGISTERED),
confirmation_state(CONFIRMATION_PENDING) {
}
Printer::RegistrationInfo::~RegistrationInfo() {
}
Printer::Printer()
: http_server_(this),
connection_state_(OFFLINE),
on_idle_posted_(false),
pending_local_settings_check_(false),
pending_print_jobs_check_(false) {
}
Printer::~Printer() {
Stop();
}
bool Printer::Start() {
if (IsRunning())
return true;
// TODO(maksymb): Add switch for command line to control interface name.
net::IPAddressNumber ip = GetLocalIp("", false);
if (ip.empty()) {
LOG(ERROR) << "No local IP found. Cannot start printer.";
return false;
}
VLOG(1) << "Local address: " << net::IPAddressToString(ip);
uint16 port = command_line_reader::ReadHttpPort(kHttpPortDefault);
// Starting HTTP server.
if (!http_server_.Start(port))
return false;
if (!LoadFromFile())
reg_info_ = RegistrationInfo();
// Starting DNS-SD server.
std::string service_name_prefix =
command_line_reader::ReadServiceNamePrefix(kServiceNamePrefixDefault);
std::string service_domain_name =
command_line_reader::ReadDomainName(kServiceDomainNameDefault);
if (!dns_server_.Start(
ServiceParameters(kServiceType, service_name_prefix, service_domain_name,
ip, port),
command_line_reader::ReadTtl(kTtlDefault),
CreateTxt())) {
http_server_.Shutdown();
return false;
}
print_job_handler_.reset(new PrintJobHandler);
xtoken_ = XPrivetToken();
starttime_ = base::Time::Now();
TryConnect();
return true;
}
bool Printer::IsRunning() const {
return print_job_handler_;
}
void Printer::Stop() {
dns_server_.Shutdown();
http_server_.Shutdown();
requester_.reset();
print_job_handler_.reset();
xmpp_listener_.reset();
}
void Printer::OnAuthError() {
LOG(ERROR) << "Auth error occurred";
access_token_update_ = base::Time::Now();
ChangeState(OFFLINE);
// TODO(maksymb): Implement *instant* updating of access_token.
}
std::string Printer::GetAccessToken() {
return access_token_;
}
PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationStart(
const std::string& user) {
PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
if (status != PrivetHttpServer::REG_ERROR_OK)
return status;
if (reg_info_.state != RegistrationInfo::DEV_REG_UNREGISTERED)
return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
reg_info_ = RegistrationInfo();
reg_info_.user = user;
reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_STARTED;
if (CommandLine::ForCurrentProcess()->HasSwitch("disable-confirmation")) {
reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_CONFIRMED;
LOG(INFO) << "Registration confirmed by default.";
} else {
printf("%s", kUserConfirmationTitle);
base::Time valid_until = base::Time::Now() +
base::TimeDelta::FromSeconds(kUserConfirmationTimeout);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&Printer::WaitUserConfirmation, AsWeakPtr(), valid_until));
}
requester_->StartRegistration(GenerateProxyId(), kPrinterName, user, kCdd);
return PrivetHttpServer::REG_ERROR_OK;
}
PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationGetClaimToken(
const std::string& user,
std::string* token,
std::string* claim_url) {
PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
if (status != PrivetHttpServer::REG_ERROR_OK)
return status;
// Check if |action=start| was called, but |action=complete| wasn't.
if (reg_info_.state != RegistrationInfo::DEV_REG_REGISTRATION_STARTED &&
reg_info_.state !=
RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY)
return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
// If |action=getClaimToken| is valid in this state (was checked above) then
// check confirmation status.
if (reg_info_.confirmation_state != RegistrationInfo::CONFIRMATION_CONFIRMED)
return ConfirmationToRegistrationError(reg_info_.confirmation_state);
// If reply wasn't received yet, reply with |pending_user_action| error.
if (reg_info_.state == RegistrationInfo::DEV_REG_REGISTRATION_STARTED)
return PrivetHttpServer::REG_ERROR_PENDING_USER_ACTION;
DCHECK_EQ(reg_info_.state,
RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY);
DCHECK_EQ(reg_info_.confirmation_state,
RegistrationInfo::CONFIRMATION_CONFIRMED);
*token = reg_info_.registration_token;
*claim_url = reg_info_.complete_invite_url;
return PrivetHttpServer::REG_ERROR_OK;
}
PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationComplete(
const std::string& user,
std::string* device_id) {
PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
if (status != PrivetHttpServer::REG_ERROR_OK)
return status;
if (reg_info_.state !=
RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY) {
return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
}
if (reg_info_.confirmation_state != RegistrationInfo::CONFIRMATION_CONFIRMED)
return ConfirmationToRegistrationError(reg_info_.confirmation_state);
reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_COMPLETING;
requester_->CompleteRegistration();
*device_id = reg_info_.device_id;
return PrivetHttpServer::REG_ERROR_OK;
}
PrivetHttpServer::RegistrationErrorStatus Printer::RegistrationCancel(
const std::string& user) {
PrivetHttpServer::RegistrationErrorStatus status = CheckCommonRegErrors(user);
if (status != PrivetHttpServer::REG_ERROR_OK &&
status != PrivetHttpServer::REG_ERROR_SERVER_ERROR) {
return status;
}
if (reg_info_.state == RegistrationInfo::DEV_REG_UNREGISTERED)
return PrivetHttpServer::REG_ERROR_INVALID_ACTION;
reg_info_ = RegistrationInfo();
requester_.reset(new CloudPrintRequester(GetTaskRunner(), this));
return PrivetHttpServer::REG_ERROR_OK;
}
void Printer::GetRegistrationServerError(std::string* description) {
DCHECK_EQ(reg_info_.state, RegistrationInfo::DEV_REG_REGISTRATION_ERROR) <<
"Method shouldn't be called when not needed.";
*description = reg_info_.error_description;
}
void Printer::CreateInfo(PrivetHttpServer::DeviceInfo* info) {
// TODO(maksymb): Replace "text" with constants.
*info = PrivetHttpServer::DeviceInfo();
info->version = "1.0";
info->name = kPrinterName;
info->description = kPrinterDescription;
info->url = kCloudPrintUrl;
info->id = reg_info_.device_id;
info->device_state = "idle";
info->connection_state = ConnectionStateToString(connection_state_);
info->manufacturer = "Google";
info->model = "Prototype";
info->serial_number = "2.3.5.7.13.17.19.31.61.89.107.127.521.607.1279.2203";
info->firmware = "3.7.31.127.8191.131071.524287.2147483647";
info->uptime = static_cast<int>((base::Time::Now() - starttime_).InSeconds());
info->x_privet_token = xtoken_.GenerateXToken();
if (reg_info_.state == RegistrationInfo::DEV_REG_UNREGISTERED)
info->api.push_back("/privet/register");
info->type.push_back("printer");
}
bool Printer::IsRegistered() const {
return reg_info_.state == RegistrationInfo::DEV_REG_REGISTERED;
}
bool Printer::CheckXPrivetTokenHeader(const std::string& token) const {
return xtoken_.CheckValidXToken(token);
}
void Printer::OnRegistrationStartResponseParsed(
const std::string& registration_token,
const std::string& complete_invite_url,
const std::string& device_id) {
reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_CLAIM_TOKEN_READY;
reg_info_.device_id = device_id;
reg_info_.registration_token = registration_token;
reg_info_.complete_invite_url = complete_invite_url;
}
void Printer::OnGetAuthCodeResponseParsed(const std::string& refresh_token,
const std::string& access_token,
int access_token_expires_in_seconds) {
reg_info_.state = RegistrationInfo::DEV_REG_REGISTERED;
reg_info_.refresh_token = refresh_token;
RememberAccessToken(access_token, access_token_expires_in_seconds);
TryConnect();
}
void Printer::OnAccesstokenReceviced(const std::string& access_token,
int expires_in_seconds) {
VLOG(3) << "Function: " << __FUNCTION__;
RememberAccessToken(access_token, expires_in_seconds);
switch (connection_state_) {
case ONLINE:
PostOnIdle();
break;
case CONNECTING:
TryConnect();
break;
default:
NOTREACHED();
}
}
void Printer::OnXmppJidReceived(const std::string& xmpp_jid) {
reg_info_.xmpp_jid = xmpp_jid;
}
void Printer::OnRegistrationError(const std::string& description) {
LOG(ERROR) << "server_error: " << description;
// TODO(maksymb): Implement waiting after error and timeout of registration.
reg_info_.state = RegistrationInfo::DEV_REG_REGISTRATION_ERROR;
reg_info_.error_description = description;
}
void Printer::OnNetworkError() {
VLOG(3) << "Function: " << __FUNCTION__;
ChangeState(OFFLINE);
}
void Printer::OnServerError(const std::string& description) {
VLOG(3) << "Function: " << __FUNCTION__;
LOG(ERROR) << "Server error: " << description;
ChangeState(OFFLINE);
}
void Printer::OnPrintJobsAvailable(const std::vector<Job>& jobs) {
VLOG(3) << "Function: " << __FUNCTION__;
LOG(INFO) << "Available printjobs: " << jobs.size();
if (jobs.empty()) {
pending_print_jobs_check_ = false;
PostOnIdle();
return;
}
LOG(INFO) << "Downloading printjob.";
requester_->RequestPrintJob(jobs[0]);
return;
}
void Printer::OnPrintJobDownloaded(const Job& job) {
VLOG(3) << "Function: " << __FUNCTION__;
print_job_handler_->SavePrintJob(
job.file,
job.ticket,
base::StringPrintf("%s.%s", job.create_time.c_str(), job.job_id.c_str()),
job.title);
requester_->SendPrintJobDone(job.job_id);
}
void Printer::OnPrintJobDone() {
VLOG(3) << "Function: " << __FUNCTION__;
PostOnIdle();
}
void Printer::OnXmppConnected() {
pending_local_settings_check_ = true;
pending_print_jobs_check_ = true;
ChangeState(ONLINE);
PostOnIdle();
}
void Printer::OnXmppAuthError() {
OnAuthError();
}
void Printer::OnXmppNetworkError() {
ChangeState(OFFLINE);
}
void Printer::OnXmppNewPrintJob(const std::string& device_id) {
DCHECK_EQ(reg_info_.device_id, device_id) << "Data should contain printer_id";
pending_print_jobs_check_ = true;
}
void Printer::OnXmppNewLocalSettings(const std::string& device_id) {
DCHECK_EQ(reg_info_.device_id, device_id) << "Data should contain printer_id";
NOTIMPLEMENTED();
}
void Printer::OnXmppDeleteNotification(const std::string& device_id) {
DCHECK_EQ(reg_info_.device_id, device_id) << "Data should contain printer_id";
NOTIMPLEMENTED();
}
void Printer::TryConnect() {
VLOG(3) << "Function: " << __FUNCTION__;
ChangeState(CONNECTING);
if (!requester_)
requester_.reset(new CloudPrintRequester(GetTaskRunner(), this));
if (IsRegistered()) {
if (access_token_update_ < base::Time::Now()) {
requester_->UpdateAccesstoken(reg_info_.refresh_token);
} else {
ConnectXmpp();
}
} else {
// TODO(maksymb): Ping google.com to check connection state.
ChangeState(ONLINE);
}
}
void Printer::ConnectXmpp() {
xmpp_listener_.reset(
new CloudPrintXmppListener(reg_info_.xmpp_jid, kXmppPingIntervalDefault,
GetTaskRunner(), this));
xmpp_listener_->Connect(access_token_);
}
void Printer::OnIdle() {
DCHECK(IsRegistered());
DCHECK(on_idle_posted_) << "Instant call is not allowed";
on_idle_posted_ = false;
if (connection_state_ != ONLINE)
return;
if (access_token_update_ < base::Time::Now()) {
requester_->UpdateAccesstoken(reg_info_.refresh_token);
return;
}
// TODO(maksymb): Check if privet-accesstoken was requested.
// TODO(maksymb): Check if local-printing was requested.
if (pending_local_settings_check_) {
GetLocalSettings();
return;
}
if (pending_print_jobs_check_) {
FetchPrintJobs();
return;
}
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&Printer::PostOnIdle, AsWeakPtr()),
base::TimeDelta::FromMilliseconds(1000));
}
void Printer::GetLocalSettings() {
DCHECK(IsRegistered());
pending_local_settings_check_ = false;
PostOnIdle();
}
void Printer::FetchPrintJobs() {
VLOG(3) << "Function: " << __FUNCTION__;
DCHECK(IsRegistered());
requester_->FetchPrintJobs(reg_info_.device_id);
}
void Printer::RememberAccessToken(const std::string& access_token,
int expires_in_seconds) {
using base::Time;
using base::TimeDelta;
access_token_ = access_token;
int64 time_to_update = static_cast<int64>(expires_in_seconds *
kTimeToNextAccessTokenUpdate);
access_token_update_ = Time::Now() + TimeDelta::FromSeconds(time_to_update);
VLOG(1) << "Current access_token: " << access_token;
SaveToFile();
}
PrivetHttpServer::RegistrationErrorStatus Printer::CheckCommonRegErrors(
const std::string& user) const {
DCHECK(!IsRegistered());
if (reg_info_.state != RegistrationInfo::DEV_REG_UNREGISTERED &&
user != reg_info_.user) {
return PrivetHttpServer::REG_ERROR_DEVICE_BUSY;
}
if (reg_info_.state == RegistrationInfo::DEV_REG_REGISTRATION_ERROR)
return PrivetHttpServer::REG_ERROR_SERVER_ERROR;
return PrivetHttpServer::REG_ERROR_OK;
}
void Printer::WaitUserConfirmation(base::Time valid_until) {
if (base::Time::Now() > valid_until) {
reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_TIMEOUT;
LOG(INFO) << "Confirmation timeout reached.";
return;
}
if (_kbhit()) {
int c = _getche();
if (c == 'y' || c == 'Y') {
reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_CONFIRMED;
LOG(INFO) << "Registration confirmed by user.";
} else {
reg_info_.confirmation_state = RegistrationInfo::CONFIRMATION_DISCARDED;
LOG(INFO) << "Registration discarded by user.";
}
return;
}
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&Printer::WaitUserConfirmation, AsWeakPtr(), valid_until),
base::TimeDelta::FromMilliseconds(100));
}
std::string Printer::GenerateProxyId() const {
return "{" + base::GenerateGUID() +"}";
}
std::vector<std::string> Printer::CreateTxt() const {
std::vector<std::string> txt;
txt.push_back("txtvers=1");
txt.push_back("ty=" + std::string(kPrinterName));
txt.push_back("note=" + std::string(kPrinterDescription));
txt.push_back("url=" + std::string(kCloudPrintUrl));
txt.push_back("type=printer");
txt.push_back("id=" + reg_info_.device_id);
txt.push_back("cs=" + ConnectionStateToString(connection_state_));
return txt;
}
void Printer::SaveToFile() const {
base::FilePath file_path;
file_path = file_path.AppendASCII(
command_line_reader::ReadStatePath(kPrinterStatePathDefault));
base::DictionaryValue json;
// TODO(maksymb): Get rid of in-place constants.
if (IsRegistered()) {
json.SetBoolean("registered", true);
json.SetString("user", reg_info_.user);
json.SetString("device_id", reg_info_.device_id);
json.SetString("refresh_token", reg_info_.refresh_token);
json.SetString("xmpp_jid", reg_info_.xmpp_jid);
json.SetString("access_token", access_token_);
json.SetInteger("access_token_update",
static_cast<int>(access_token_update_.ToTimeT()));
} else {
json.SetBoolean("registered", false);
}
std::string json_str;
base::JSONWriter::WriteWithOptions(&json,
base::JSONWriter::OPTIONS_PRETTY_PRINT,
&json_str);
if (!file_util::WriteFile(file_path, json_str.data(),
static_cast<int>(json_str.size()))) {
LOG(ERROR) << "Cannot write state.";
}
LOG(INFO) << "State written to file.";
}
bool Printer::LoadFromFile() {
base::FilePath file_path;
file_path = file_path.AppendASCII(
command_line_reader::ReadStatePath(kPrinterStatePathDefault));
if (!base::PathExists(file_path)) {
LOG(INFO) << "Registration info is not found. Printer is unregistered.";
return false;
}
LOG(INFO) << "Loading registration info from file.";
std::string json_str;
if (!file_util::ReadFileToString(file_path, &json_str)) {
LOG(ERROR) << "Cannot open file.";
return false;
}
scoped_ptr<base::Value> json_val(base::JSONReader::Read(json_str));
base::DictionaryValue* json = NULL;
if (!json_val || !json_val->GetAsDictionary(&json)) {
LOG(ERROR) << "Cannot read JSON dictionary from file.";
return false;
}
bool registered = false;
if (!json->GetBoolean("registered", &registered)) {
LOG(ERROR) << "Cannot parse |registered| state.";
return false;
}
if (!registered) {
reg_info_ = RegistrationInfo();
return true;
}
std::string user;
if (!json->GetString("user", &user)) {
LOG(ERROR) << "Cannot parse |user|.";
return false;
}
std::string device_id;
if (!json->GetString("device_id", &device_id)) {
LOG(ERROR) << "Cannot parse |device_id|.";
return false;
}
std::string refresh_token;
if (!json->GetString("refresh_token", &refresh_token)) {
LOG(ERROR) << "Cannot parse |refresh_token|.";
return false;
}
std::string xmpp_jid;
if (!json->GetString("xmpp_jid", &xmpp_jid)) {
LOG(ERROR) << "Cannot parse |xmpp_jid|.";
return false;
}
std::string access_token;
if (!json->GetString("access_token", &access_token)) {
LOG(ERROR) << "Cannot parse |access_token|.";
return false;
}
int access_token_update;
if (!json->GetInteger("access_token_update", &access_token_update)) {
LOG(ERROR) << "Cannot parse |access_token_update|.";
return false;
}
reg_info_ = RegistrationInfo();
reg_info_.state = RegistrationInfo::DEV_REG_REGISTERED;
reg_info_.user = user;
reg_info_.device_id = device_id;
reg_info_.refresh_token = refresh_token;
reg_info_.xmpp_jid = xmpp_jid;
using base::Time;
access_token_ = access_token;
access_token_update_ = Time::FromTimeT(access_token_update);
return true;
}
void Printer::PostOnIdle() {
VLOG(3) << "Function: " << __FUNCTION__;
DCHECK(!on_idle_posted_) << "Only one instance can be posted.";
on_idle_posted_ = true;
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&Printer::OnIdle, AsWeakPtr()));
}
PrivetHttpServer::RegistrationErrorStatus
Printer::ConfirmationToRegistrationError(
RegistrationInfo::ConfirmationState state) {
switch (state) {
case RegistrationInfo::CONFIRMATION_PENDING:
return PrivetHttpServer::REG_ERROR_PENDING_USER_ACTION;
case RegistrationInfo::CONFIRMATION_DISCARDED:
return PrivetHttpServer::REG_ERROR_USER_CANCEL;
case RegistrationInfo::CONFIRMATION_CONFIRMED:
NOTREACHED();
return PrivetHttpServer::REG_ERROR_OK;
case RegistrationInfo::CONFIRMATION_TIMEOUT:
return PrivetHttpServer::REG_ERROR_CONFIRMATION_TIMEOUT;
default:
NOTREACHED();
return PrivetHttpServer::REG_ERROR_OK;
}
}
std::string Printer::ConnectionStateToString(ConnectionState state) const {
switch (state) {
case OFFLINE:
return "offline";
case ONLINE:
return "online";
case CONNECTING:
return "connecting";
case NOT_CONFIGURED:
return "not-configured";
default:
NOTREACHED();
return "";
}
}
bool Printer::ChangeState(ConnectionState new_state) {
if (connection_state_ == new_state)
return false;
connection_state_ = new_state;
LOG(INFO) << base::StringPrintf(
"Printer is now %s (%s)",
ConnectionStateToString(connection_state_).c_str(),
IsRegistered() ? "registered" : "unregistered");
dns_server_.UpdateMetadata(CreateTxt());
switch (connection_state_) {
case CONNECTING:
break;
case ONLINE:
break;
case OFFLINE:
requester_.reset();
xmpp_listener_.reset();
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&Printer::TryConnect, AsWeakPtr()),
base::TimeDelta::FromSeconds(kReconnectTimeout));
case NOT_CONFIGURED:
break;
default:
NOTREACHED();
}
return true;
}