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.
5 #include "chrome/service/cloud_print/cloud_print_connector.h"
8 #include "base/bind_helpers.h"
10 #include "base/rand_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/values.h"
17 #include "chrome/common/cloud_print/cloud_print_constants.h"
18 #include "chrome/common/cloud_print/cloud_print_helpers.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "chrome/service/cloud_print/cloud_print_service_helpers.h"
21 #include "net/base/mime_util.h"
22 #include "ui/base/l10n/l10n_util.h"
24 namespace cloud_print
{
26 CloudPrintConnector::CloudPrintConnector(Client
* client
,
27 const ConnectorSettings
& settings
)
29 next_response_handler_(NULL
),
30 stats_ptr_factory_(this) {
31 settings_
.CopyFrom(settings
);
34 bool CloudPrintConnector::InitPrintSystem() {
35 if (print_system_
.get())
37 print_system_
= PrintSystem::CreateInstance(
38 settings_
.print_system_settings());
39 if (!print_system_
.get()) {
41 return false; // No memory.
43 PrintSystem::PrintSystemResult result
= print_system_
->Init();
44 if (!result
.succeeded()) {
46 // We could not initialize the print system. We need to notify the server.
47 ReportUserMessage(kPrintSystemFailedMessageId
, result
.message());
53 void CloudPrintConnector::ScheduleStatsReport() {
54 base::MessageLoop::current()->PostDelayedTask(
56 base::Bind(&CloudPrintConnector::ReportStats
,
57 stats_ptr_factory_
.GetWeakPtr()),
58 base::TimeDelta::FromHours(1));
61 void CloudPrintConnector::ReportStats() {
62 PrinterJobHandler::ReportsStats();
63 ScheduleStatsReport();
66 bool CloudPrintConnector::Start() {
67 VLOG(1) << "CP_CONNECTOR: Starting connector"
68 << ", proxy id: " << settings_
.proxy_id();
70 pending_tasks_
.clear();
72 if (!InitPrintSystem())
75 ScheduleStatsReport();
77 // Start watching for updates from the print system.
78 print_server_watcher_
= print_system_
->CreatePrintServerWatcher();
79 print_server_watcher_
->StartWatching(this);
81 // Get list of registered printers.
82 AddPendingAvailableTask();
86 void CloudPrintConnector::Stop() {
87 VLOG(1) << "CP_CONNECTOR: Stopping connector"
88 << ", proxy id: " << settings_
.proxy_id();
90 // Do uninitialization here.
91 stats_ptr_factory_
.InvalidateWeakPtrs();
92 pending_tasks_
.clear();
93 print_server_watcher_
= NULL
;
97 bool CloudPrintConnector::IsRunning() {
98 return print_server_watcher_
.get() != NULL
;
101 void CloudPrintConnector::GetPrinterIds(std::list
<std::string
>* printer_ids
) {
103 printer_ids
->clear();
104 for (JobHandlerMap::const_iterator iter
= job_handler_map_
.begin();
105 iter
!= job_handler_map_
.end(); ++iter
) {
106 printer_ids
->push_back(iter
->first
);
110 void CloudPrintConnector::RegisterPrinters(
111 const printing::PrinterList
& printers
) {
114 printing::PrinterList::const_iterator it
;
115 for (it
= printers
.begin(); it
!= printers
.end(); ++it
) {
116 if (settings_
.ShouldConnect(it
->printer_name
))
117 AddPendingRegisterTask(*it
);
121 // Check for jobs for specific printer
122 void CloudPrintConnector::CheckForJobs(const std::string
& reason
,
123 const std::string
& printer_id
) {
126 if (!printer_id
.empty()) {
127 JobHandlerMap::iterator index
= job_handler_map_
.find(printer_id
);
128 if (index
!= job_handler_map_
.end()) {
129 index
->second
->CheckForJobs(reason
);
131 std::string status_message
= l10n_util::GetStringUTF8(
132 IDS_CLOUD_PRINT_ZOMBIE_PRINTER
);
133 LOG(ERROR
) << "CP_CONNECTOR: " << status_message
<<
134 " Printer_id: " << printer_id
;
135 ReportUserMessage(kZombiePrinterMessageId
, status_message
);
138 for (JobHandlerMap::iterator index
= job_handler_map_
.begin();
139 index
!= job_handler_map_
.end(); index
++) {
140 index
->second
->CheckForJobs(reason
);
145 void CloudPrintConnector::UpdatePrinterSettings(const std::string
& printer_id
) {
146 // Since connector is managing many printers we need to go through all of them
147 // to select the correct settings.
148 GURL printer_list_url
= GetUrlForPrinterList(
149 settings_
.server_url(), settings_
.proxy_id());
152 kCloudPrintRegisterMaxRetryCount
,
153 &CloudPrintConnector::HandlePrinterListResponseSettingsUpdate
);
156 void CloudPrintConnector::OnPrinterAdded() {
157 AddPendingAvailableTask();
160 void CloudPrintConnector::OnPrinterDeleted(const std::string
& printer_id
) {
161 AddPendingDeleteTask(printer_id
);
164 void CloudPrintConnector::OnAuthError() {
165 client_
->OnAuthFailed();
168 // CloudPrintURLFetcher::Delegate implementation.
169 CloudPrintURLFetcher::ResponseAction
CloudPrintConnector::HandleRawData(
170 const net::URLFetcher
* source
,
172 const std::string
& data
) {
173 // If this notification came as a result of user message call, stop it.
174 // Otherwise proceed continue processing.
175 if (user_message_request_
.get() &&
176 user_message_request_
->IsSameRequest(source
))
177 return CloudPrintURLFetcher::STOP_PROCESSING
;
178 return CloudPrintURLFetcher::CONTINUE_PROCESSING
;
181 CloudPrintURLFetcher::ResponseAction
CloudPrintConnector::HandleJSONData(
182 const net::URLFetcher
* source
,
184 base::DictionaryValue
* json_data
,
186 if (!IsRunning()) // Orphant response. Connector has been stopped already.
187 return CloudPrintURLFetcher::STOP_PROCESSING
;
189 DCHECK(next_response_handler_
);
190 return (this->*next_response_handler_
)(source
, url
, json_data
, succeeded
);
193 CloudPrintURLFetcher::ResponseAction
CloudPrintConnector::OnRequestAuthError() {
195 return CloudPrintURLFetcher::STOP_PROCESSING
;
198 std::string
CloudPrintConnector::GetAuthHeader() {
199 return GetCloudPrintAuthHeaderFromStore();
202 CloudPrintConnector::~CloudPrintConnector() {}
204 CloudPrintURLFetcher::ResponseAction
205 CloudPrintConnector::HandlePrinterListResponse(
206 const net::URLFetcher
* source
,
208 base::DictionaryValue
* json_data
,
212 return CloudPrintURLFetcher::RETRY_REQUEST
;
214 UpdateSettingsFromPrintersList(json_data
);
216 // Now we need to get the list of printers from the print system
217 // and split printers into 3 categories:
218 // - existing and registered printers
220 // - deleted printers
222 // Get list of the printers from the print system.
223 printing::PrinterList local_printers
;
224 PrintSystem::PrintSystemResult result
=
225 print_system_
->EnumeratePrinters(&local_printers
);
226 bool full_list
= result
.succeeded();
228 std::string message
= result
.message();
230 message
= l10n_util::GetStringFUTF8(IDS_CLOUD_PRINT_ENUM_FAILED
,
231 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
));
232 // There was a failure enumerating printers. Send a message to the server.
233 ReportUserMessage(kEnumPrintersFailedMessageId
, message
);
236 // Go through the list of the cloud printers and init print job handlers.
237 base::ListValue
* printer_list
= NULL
;
238 // There may be no "printers" value in the JSON
239 if (json_data
->GetList(kPrinterListValue
, &printer_list
) && printer_list
) {
240 for (size_t index
= 0; index
< printer_list
->GetSize(); index
++) {
241 base::DictionaryValue
* printer_data
= NULL
;
242 if (printer_list
->GetDictionary(index
, &printer_data
)) {
243 std::string printer_name
;
244 printer_data
->GetString(kNameValue
, &printer_name
);
245 std::string printer_id
;
246 printer_data
->GetString(kIdValue
, &printer_id
);
248 if (!settings_
.ShouldConnect(printer_name
)) {
249 VLOG(1) << "CP_CONNECTOR: Deleting " << printer_name
<<
250 " id: " << printer_id
<< " as blacklisted";
251 AddPendingDeleteTask(printer_id
);
252 } else if (RemovePrinterFromList(printer_name
, &local_printers
)) {
253 InitJobHandlerForPrinter(printer_data
);
255 // Cloud printer is not found on the local system.
256 if (full_list
|| settings_
.delete_on_enum_fail()) {
257 // Delete if we get the full list of printers or
258 // |delete_on_enum_fail_| is set.
259 VLOG(1) << "CP_CONNECTOR: Deleting " << printer_name
<<
260 " id: " << printer_id
<<
261 " full_list: " << full_list
<<
262 " delete_on_enum_fail: " << settings_
.delete_on_enum_fail();
263 AddPendingDeleteTask(printer_id
);
265 LOG(ERROR
) << "CP_CONNECTOR: Printer: " << printer_name
<<
266 " id: " << printer_id
<<
267 " not found in print system and full printer list was" <<
268 " not received. Printer will not be able to process" <<
280 RegisterPrinters(local_printers
);
281 ContinuePendingTaskProcessing(); // Continue processing background tasks.
282 return CloudPrintURLFetcher::STOP_PROCESSING
;
285 CloudPrintURLFetcher::ResponseAction
286 CloudPrintConnector::HandlePrinterListResponseSettingsUpdate(
287 const net::URLFetcher
* source
,
289 base::DictionaryValue
* json_data
,
293 return CloudPrintURLFetcher::RETRY_REQUEST
;
295 UpdateSettingsFromPrintersList(json_data
);
296 return CloudPrintURLFetcher::STOP_PROCESSING
;
299 CloudPrintURLFetcher::ResponseAction
300 CloudPrintConnector::HandlePrinterDeleteResponse(
301 const net::URLFetcher
* source
,
303 base::DictionaryValue
* json_data
,
305 VLOG(1) << "CP_CONNECTOR: Handler printer delete response"
306 << ", succeeded: " << succeeded
308 ContinuePendingTaskProcessing(); // Continue processing background tasks.
309 return CloudPrintURLFetcher::STOP_PROCESSING
;
312 CloudPrintURLFetcher::ResponseAction
313 CloudPrintConnector::HandleRegisterPrinterResponse(
314 const net::URLFetcher
* source
,
316 base::DictionaryValue
* json_data
,
318 VLOG(1) << "CP_CONNECTOR: Handler printer register response"
319 << ", succeeded: " << succeeded
322 base::ListValue
* printer_list
= NULL
;
323 // There should be a "printers" value in the JSON
324 if (json_data
->GetList(kPrinterListValue
, &printer_list
)) {
325 base::DictionaryValue
* printer_data
= NULL
;
326 if (printer_list
->GetDictionary(0, &printer_data
))
327 InitJobHandlerForPrinter(printer_data
);
330 ContinuePendingTaskProcessing(); // Continue processing background tasks.
331 return CloudPrintURLFetcher::STOP_PROCESSING
;
335 void CloudPrintConnector::StartGetRequest(const GURL
& url
,
337 ResponseHandler handler
) {
338 next_response_handler_
= handler
;
339 request_
= CloudPrintURLFetcher::Create();
340 request_
->StartGetRequest(CloudPrintURLFetcher::REQUEST_UPDATE_JOB
,
341 url
, this, max_retries
, std::string());
344 void CloudPrintConnector::StartPostRequest(
345 CloudPrintURLFetcher::RequestType type
,
348 const std::string
& mime_type
,
349 const std::string
& post_data
,
350 ResponseHandler handler
) {
351 next_response_handler_
= handler
;
352 request_
= CloudPrintURLFetcher::Create();
353 request_
->StartPostRequest(
354 type
, url
, this, max_retries
, mime_type
, post_data
, std::string());
357 void CloudPrintConnector::ReportUserMessage(const std::string
& message_id
,
358 const std::string
& failure_msg
) {
359 // This is a fire and forget type of function.
360 // Result of this request will be ignored.
361 std::string mime_boundary
;
362 CreateMimeBoundaryForUpload(&mime_boundary
);
363 GURL url
= GetUrlForUserMessage(settings_
.server_url(), message_id
);
364 std::string post_data
;
365 net::AddMultipartValueForUpload(kMessageTextValue
, failure_msg
, mime_boundary
,
366 std::string(), &post_data
);
367 net::AddMultipartFinalDelimiterForUpload(mime_boundary
, &post_data
);
368 std::string
mime_type("multipart/form-data; boundary=");
369 mime_type
+= mime_boundary
;
370 user_message_request_
= CloudPrintURLFetcher::Create();
371 user_message_request_
->StartPostRequest(
372 CloudPrintURLFetcher::REQUEST_USER_MESSAGE
, url
, this, 1, mime_type
,
373 post_data
, std::string());
376 bool CloudPrintConnector::RemovePrinterFromList(
377 const std::string
& printer_name
,
378 printing::PrinterList
* printer_list
) {
379 for (printing::PrinterList::iterator index
= printer_list
->begin();
380 index
!= printer_list
->end(); index
++) {
381 if (IsSamePrinter(index
->printer_name
, printer_name
)) {
382 index
= printer_list
->erase(index
);
389 void CloudPrintConnector::InitJobHandlerForPrinter(
390 base::DictionaryValue
* printer_data
) {
391 DCHECK(printer_data
);
392 PrinterJobHandler::PrinterInfoFromCloud printer_info_cloud
;
393 printer_data
->GetString(kIdValue
, &printer_info_cloud
.printer_id
);
394 DCHECK(!printer_info_cloud
.printer_id
.empty());
395 VLOG(1) << "CP_CONNECTOR: Init job handler"
396 << ", printer id: " << printer_info_cloud
.printer_id
;
397 JobHandlerMap::iterator index
= job_handler_map_
.find(
398 printer_info_cloud
.printer_id
);
399 if (index
!= job_handler_map_
.end())
400 return; // Nothing to do if we already have a job handler for this printer.
402 printing::PrinterBasicInfo printer_info
;
403 printer_data
->GetString(kNameValue
, &printer_info
.printer_name
);
404 DCHECK(!printer_info
.printer_name
.empty());
405 printer_data
->GetString(kPrinterDescValue
,
406 &printer_info
.printer_description
);
407 // Printer status is a string value which actually contains an integer.
408 std::string printer_status
;
409 if (printer_data
->GetString(kPrinterStatusValue
, &printer_status
)) {
410 base::StringToInt(printer_status
, &printer_info
.printer_status
);
412 printer_data
->GetString(kPrinterCapsHashValue
,
413 &printer_info_cloud
.caps_hash
);
414 base::ListValue
* tags_list
= NULL
;
415 if (printer_data
->GetList(kTagsValue
, &tags_list
) && tags_list
) {
416 for (size_t index
= 0; index
< tags_list
->GetSize(); index
++) {
418 if (tags_list
->GetString(index
, &tag
) &&
419 StartsWithASCII(tag
, kCloudPrintServiceTagsHashTagName
, false)) {
420 std::vector
<std::string
> tag_parts
;
421 base::SplitStringDontTrim(tag
, '=', &tag_parts
);
422 DCHECK_EQ(tag_parts
.size(), 2U);
423 if (tag_parts
.size() == 2)
424 printer_info_cloud
.tags_hash
= tag_parts
[1];
429 int xmpp_timeout
= 0;
430 printer_data
->GetInteger(kLocalSettingsPendingXmppValue
, &xmpp_timeout
);
431 printer_info_cloud
.current_xmpp_timeout
= settings_
.xmpp_ping_timeout_sec();
432 printer_info_cloud
.pending_xmpp_timeout
= xmpp_timeout
;
434 scoped_refptr
<PrinterJobHandler
> job_handler
;
435 job_handler
= new PrinterJobHandler(printer_info
,
437 settings_
.server_url(),
440 job_handler_map_
[printer_info_cloud
.printer_id
] = job_handler
;
441 job_handler
->Initialize();
444 void CloudPrintConnector::UpdateSettingsFromPrintersList(
445 base::DictionaryValue
* json_data
) {
446 base::ListValue
* printer_list
= NULL
;
447 int min_xmpp_timeout
= std::numeric_limits
<int>::max();
448 // There may be no "printers" value in the JSON
449 if (json_data
->GetList(kPrinterListValue
, &printer_list
) && printer_list
) {
450 for (size_t index
= 0; index
< printer_list
->GetSize(); index
++) {
451 base::DictionaryValue
* printer_data
= NULL
;
452 if (printer_list
->GetDictionary(index
, &printer_data
)) {
453 int xmpp_timeout
= 0;
454 if (printer_data
->GetInteger(kLocalSettingsPendingXmppValue
,
456 min_xmpp_timeout
= std::min(xmpp_timeout
, min_xmpp_timeout
);
462 if (min_xmpp_timeout
!= std::numeric_limits
<int>::max()) {
463 DCHECK(min_xmpp_timeout
>= kMinXmppPingTimeoutSecs
);
464 settings_
.SetXmppPingTimeoutSec(min_xmpp_timeout
);
465 client_
->OnXmppPingUpdated(min_xmpp_timeout
);
470 void CloudPrintConnector::AddPendingAvailableTask() {
472 task
.type
= PENDING_PRINTERS_AVAILABLE
;
473 AddPendingTask(task
);
476 void CloudPrintConnector::AddPendingDeleteTask(const std::string
& id
) {
478 task
.type
= PENDING_PRINTER_DELETE
;
479 task
.printer_id
= id
;
480 AddPendingTask(task
);
483 void CloudPrintConnector::AddPendingRegisterTask(
484 const printing::PrinterBasicInfo
& info
) {
486 task
.type
= PENDING_PRINTER_REGISTER
;
487 task
.printer_info
= info
;
488 AddPendingTask(task
);
491 void CloudPrintConnector::AddPendingTask(const PendingTask
& task
) {
492 pending_tasks_
.push_back(task
);
493 // If this is the only pending task, we need to start the process.
494 if (pending_tasks_
.size() == 1) {
495 base::MessageLoop::current()->PostTask(
496 FROM_HERE
, base::Bind(&CloudPrintConnector::ProcessPendingTask
, this));
500 void CloudPrintConnector::ProcessPendingTask() {
502 return; // Orphant call.
503 if (pending_tasks_
.size() == 0)
504 return; // No peding tasks.
506 PendingTask task
= pending_tasks_
.front();
509 case PENDING_PRINTERS_AVAILABLE
:
510 OnPrintersAvailable();
512 case PENDING_PRINTER_REGISTER
:
513 OnPrinterRegister(task
.printer_info
);
515 case PENDING_PRINTER_DELETE
:
516 OnPrinterDelete(task
.printer_id
);
523 void CloudPrintConnector::ContinuePendingTaskProcessing() {
524 if (pending_tasks_
.size() == 0)
525 return; // No pending tasks.
527 // Delete current task and repost if we have more task available.
528 pending_tasks_
.pop_front();
529 if (pending_tasks_
.size() != 0) {
530 base::MessageLoop::current()->PostTask(
531 FROM_HERE
, base::Bind(&CloudPrintConnector::ProcessPendingTask
, this));
535 void CloudPrintConnector::OnPrintersAvailable() {
536 GURL printer_list_url
= GetUrlForPrinterList(
537 settings_
.server_url(), settings_
.proxy_id());
538 StartGetRequest(printer_list_url
,
539 kCloudPrintRegisterMaxRetryCount
,
540 &CloudPrintConnector::HandlePrinterListResponse
);
543 void CloudPrintConnector::OnPrinterRegister(
544 const printing::PrinterBasicInfo
& info
) {
545 for (JobHandlerMap::iterator it
= job_handler_map_
.begin();
546 it
!= job_handler_map_
.end(); ++it
) {
547 if (IsSamePrinter(it
->second
->GetPrinterName(), info
.printer_name
)) {
548 // Printer already registered, continue to the next task.
549 ContinuePendingTaskProcessing();
554 // Asynchronously fetch the printer caps and defaults. The story will
555 // continue in OnReceivePrinterCaps.
556 print_system_
->GetPrinterCapsAndDefaults(
557 info
.printer_name
.c_str(),
558 base::Bind(&CloudPrintConnector::OnReceivePrinterCaps
,
559 base::Unretained(this)));
562 void CloudPrintConnector::OnPrinterDelete(const std::string
& printer_id
) {
563 // Remove corresponding printer job handler.
564 JobHandlerMap::iterator it
= job_handler_map_
.find(printer_id
);
565 if (it
!= job_handler_map_
.end()) {
566 it
->second
->Shutdown();
567 job_handler_map_
.erase(it
);
570 // TODO(gene): We probably should not try indefinitely here. Just once or
571 // twice should be enough.
572 // Bug: http://code.google.com/p/chromium/issues/detail?id=101850
573 GURL url
= GetUrlForPrinterDelete(
574 settings_
.server_url(), printer_id
, "printer_deleted");
576 kCloudPrintAPIMaxRetryCount
,
577 &CloudPrintConnector::HandlePrinterDeleteResponse
);
580 void CloudPrintConnector::OnReceivePrinterCaps(
582 const std::string
& printer_name
,
583 const printing::PrinterCapsAndDefaults
& caps_and_defaults
) {
585 return; // Orphant call.
586 DCHECK(pending_tasks_
.size() > 0 &&
587 pending_tasks_
.front().type
== PENDING_PRINTER_REGISTER
);
590 LOG(ERROR
) << "CP_CONNECTOR: Failed to get printer info"
591 << ", printer name: " << printer_name
;
592 // This printer failed to register, notify the server of this failure.
593 base::string16 printer_name_utf16
= base::UTF8ToUTF16(printer_name
);
594 std::string status_message
= l10n_util::GetStringFUTF8(
595 IDS_CLOUD_PRINT_REGISTER_PRINTER_FAILED
,
597 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
));
598 ReportUserMessage(kGetPrinterCapsFailedMessageId
, status_message
);
600 ContinuePendingTaskProcessing(); // Skip this printer registration.
604 const printing::PrinterBasicInfo
& info
= pending_tasks_
.front().printer_info
;
605 DCHECK(IsSamePrinter(info
.printer_name
, printer_name
));
607 std::string mime_boundary
;
608 CreateMimeBoundaryForUpload(&mime_boundary
);
609 std::string post_data
;
611 net::AddMultipartValueForUpload(kProxyIdValue
,
612 settings_
.proxy_id(), mime_boundary
, std::string(), &post_data
);
613 net::AddMultipartValueForUpload(kPrinterNameValue
,
614 info
.printer_name
, mime_boundary
, std::string(), &post_data
);
615 net::AddMultipartValueForUpload(kPrinterDescValue
,
616 info
.printer_description
, mime_boundary
, std::string(), &post_data
);
617 net::AddMultipartValueForUpload(kPrinterStatusValue
,
618 base::StringPrintf("%d", info
.printer_status
),
619 mime_boundary
, std::string(), &post_data
);
620 // Add local_settings with a current XMPP ping interval.
621 net::AddMultipartValueForUpload(kPrinterLocalSettingsValue
,
622 base::StringPrintf(kCreateLocalSettingsXmppPingFormat
,
623 settings_
.xmpp_ping_timeout_sec()),
624 mime_boundary
, std::string(), &post_data
);
625 post_data
+= GetPostDataForPrinterInfo(info
, mime_boundary
);
626 if (caps_and_defaults
.caps_mime_type
== kContentTypeJSON
) {
627 net::AddMultipartValueForUpload(kUseCDD
, "true", mime_boundary
,
628 std::string(), &post_data
);
630 net::AddMultipartValueForUpload(kPrinterCapsValue
,
631 caps_and_defaults
.printer_capabilities
, mime_boundary
,
632 caps_and_defaults
.caps_mime_type
, &post_data
);
633 net::AddMultipartValueForUpload(kPrinterDefaultsValue
,
634 caps_and_defaults
.printer_defaults
, mime_boundary
,
635 caps_and_defaults
.defaults_mime_type
, &post_data
);
636 // Send a hash of the printer capabilities to the server. We will use this
637 // later to check if the capabilities have changed
638 net::AddMultipartValueForUpload(kPrinterCapsHashValue
,
639 base::MD5String(caps_and_defaults
.printer_capabilities
),
640 mime_boundary
, std::string(), &post_data
);
641 net::AddMultipartFinalDelimiterForUpload(mime_boundary
, &post_data
);
642 std::string
mime_type("multipart/form-data; boundary=");
643 mime_type
+= mime_boundary
;
645 GURL post_url
= GetUrlForPrinterRegistration(settings_
.server_url());
646 StartPostRequest(CloudPrintURLFetcher::REQUEST_REGISTER
, post_url
,
647 kCloudPrintAPIMaxRetryCount
, mime_type
, post_data
,
648 &CloudPrintConnector::HandleRegisterPrinterResponse
);
651 bool CloudPrintConnector::IsSamePrinter(const std::string
& name1
,
652 const std::string
& name2
) const {
653 return (0 == base::strcasecmp(name1
.c_str(), name2
.c_str()));
656 } // namespace cloud_print