Home | History | Annotate | Download | only in cloud_print
      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 "chrome/service/cloud_print/cloud_print_connector.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/md5.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/stringprintf.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "base/values.h"
     16 #include "chrome/common/cloud_print/cloud_print_constants.h"
     17 #include "chrome/common/cloud_print/cloud_print_helpers.h"
     18 #include "chrome/service/cloud_print/cloud_print_helpers.h"
     19 #include "grit/generated_resources.h"
     20 #include "net/base/mime_util.h"
     21 #include "ui/base/l10n/l10n_util.h"
     22 
     23 namespace cloud_print {
     24 
     25 CloudPrintConnector::CloudPrintConnector(Client* client,
     26                                          const ConnectorSettings& settings)
     27   : client_(client),
     28     next_response_handler_(NULL) {
     29   settings_.CopyFrom(settings);
     30 }
     31 
     32 bool CloudPrintConnector::InitPrintSystem() {
     33   if (print_system_.get())
     34     return true;
     35   print_system_ = PrintSystem::CreateInstance(
     36       settings_.print_system_settings());
     37   if (!print_system_.get()) {
     38     NOTREACHED();
     39     return false;  // No memory.
     40   }
     41   PrintSystem::PrintSystemResult result = print_system_->Init();
     42   if (!result.succeeded()) {
     43     print_system_ = NULL;
     44     // We could not initialize the print system. We need to notify the server.
     45     ReportUserMessage(kPrintSystemFailedMessageId, result.message());
     46     return false;
     47   }
     48   return true;
     49 }
     50 
     51 bool CloudPrintConnector::Start() {
     52   VLOG(1) << "CP_CONNECTOR: Starting connector"
     53           << ", proxy id: " << settings_.proxy_id();
     54 
     55   pending_tasks_.clear();
     56 
     57   if (!InitPrintSystem())
     58     return false;
     59 
     60   // Start watching for updates from the print system.
     61   print_server_watcher_ = print_system_->CreatePrintServerWatcher();
     62   print_server_watcher_->StartWatching(this);
     63 
     64   // Get list of registered printers.
     65   AddPendingAvailableTask();
     66   return true;
     67 }
     68 
     69 void CloudPrintConnector::Stop() {
     70   VLOG(1) << "CP_CONNECTOR: Stopping connector"
     71           << ", proxy id: " << settings_.proxy_id();
     72   DCHECK(IsRunning());
     73   // Do uninitialization here.
     74   pending_tasks_.clear();
     75   print_server_watcher_ = NULL;
     76   request_ = NULL;
     77 }
     78 
     79 bool CloudPrintConnector::IsRunning() {
     80   return print_server_watcher_.get() != NULL;
     81 }
     82 
     83 void CloudPrintConnector::GetPrinterIds(std::list<std::string>* printer_ids) {
     84   DCHECK(printer_ids);
     85   printer_ids->clear();
     86   for (JobHandlerMap::const_iterator iter = job_handler_map_.begin();
     87        iter != job_handler_map_.end(); ++iter) {
     88     printer_ids->push_back(iter->first);
     89   }
     90 }
     91 
     92 void CloudPrintConnector::RegisterPrinters(
     93     const printing::PrinterList& printers) {
     94   if (!IsRunning())
     95     return;
     96   printing::PrinterList::const_iterator it;
     97   for (it = printers.begin(); it != printers.end(); ++it) {
     98     if (settings_.ShouldConnect(it->printer_name))
     99       AddPendingRegisterTask(*it);
    100   }
    101 }
    102 
    103 // Check for jobs for specific printer
    104 void CloudPrintConnector::CheckForJobs(const std::string& reason,
    105                                        const std::string& printer_id) {
    106   if (!IsRunning())
    107     return;
    108   if (!printer_id.empty()) {
    109     JobHandlerMap::iterator index = job_handler_map_.find(printer_id);
    110     if (index != job_handler_map_.end()) {
    111       index->second->CheckForJobs(reason);
    112     } else {
    113       std::string status_message = l10n_util::GetStringUTF8(
    114           IDS_CLOUD_PRINT_ZOMBIE_PRINTER);
    115       LOG(ERROR) << "CP_CONNECTOR: " << status_message <<
    116           " Printer_id: " << printer_id;
    117       ReportUserMessage(kZombiePrinterMessageId, status_message);
    118     }
    119   } else {
    120     for (JobHandlerMap::iterator index = job_handler_map_.begin();
    121          index != job_handler_map_.end(); index++) {
    122       index->second->CheckForJobs(reason);
    123     }
    124   }
    125 }
    126 
    127 void CloudPrintConnector::OnPrinterAdded() {
    128   AddPendingAvailableTask();
    129 }
    130 
    131 void CloudPrintConnector::OnPrinterDeleted(const std::string& printer_id) {
    132   AddPendingDeleteTask(printer_id);
    133 }
    134 
    135 void CloudPrintConnector::OnAuthError() {
    136   client_->OnAuthFailed();
    137 }
    138 
    139 // CloudPrintURLFetcher::Delegate implementation.
    140 CloudPrintURLFetcher::ResponseAction CloudPrintConnector::HandleRawData(
    141     const net::URLFetcher* source,
    142     const GURL& url,
    143     const std::string& data) {
    144   // If this notification came as a result of user message call, stop it.
    145   // Otherwise proceed continue processing.
    146   if (user_message_request_.get() &&
    147       user_message_request_->IsSameRequest(source))
    148     return CloudPrintURLFetcher::STOP_PROCESSING;
    149   return CloudPrintURLFetcher::CONTINUE_PROCESSING;
    150 }
    151 
    152 CloudPrintURLFetcher::ResponseAction CloudPrintConnector::HandleJSONData(
    153     const net::URLFetcher* source,
    154     const GURL& url,
    155     DictionaryValue* json_data,
    156     bool succeeded) {
    157   if (!IsRunning())  // Orphant response. Connector has been stopped already.
    158     return CloudPrintURLFetcher::STOP_PROCESSING;
    159 
    160   DCHECK(next_response_handler_);
    161   return (this->*next_response_handler_)(source, url, json_data, succeeded);
    162 }
    163 
    164 CloudPrintURLFetcher::ResponseAction CloudPrintConnector::OnRequestAuthError() {
    165   OnAuthError();
    166   return CloudPrintURLFetcher::STOP_PROCESSING;
    167 }
    168 
    169 std::string CloudPrintConnector::GetAuthHeader() {
    170   return GetCloudPrintAuthHeaderFromStore();
    171 }
    172 
    173 CloudPrintConnector::~CloudPrintConnector() {}
    174 
    175 CloudPrintURLFetcher::ResponseAction
    176 CloudPrintConnector::HandlePrinterListResponse(
    177     const net::URLFetcher* source,
    178     const GURL& url,
    179     DictionaryValue* json_data,
    180     bool succeeded) {
    181   DCHECK(succeeded);
    182   if (!succeeded)
    183     return CloudPrintURLFetcher::RETRY_REQUEST;
    184 
    185   // Now we need to get the list of printers from the print system
    186   // and split printers into 3 categories:
    187   // - existing and registered printers
    188   // - new printers
    189   // - deleted printers
    190 
    191   // Get list of the printers from the print system.
    192   printing::PrinterList local_printers;
    193   PrintSystem::PrintSystemResult result =
    194       print_system_->EnumeratePrinters(&local_printers);
    195   bool full_list = result.succeeded();
    196   if (!full_list) {
    197     std::string message = result.message();
    198     if (message.empty())
    199       message = l10n_util::GetStringFUTF8(IDS_CLOUD_PRINT_ENUM_FAILED,
    200           l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT));
    201     // There was a failure enumerating printers. Send a message to the server.
    202     ReportUserMessage(kEnumPrintersFailedMessageId, message);
    203   }
    204 
    205   // Go through the list of the cloud printers and init print job handlers.
    206   ListValue* printer_list = NULL;
    207   // There may be no "printers" value in the JSON
    208   if (json_data->GetList(kPrinterListValue, &printer_list) && printer_list) {
    209     for (size_t index = 0; index < printer_list->GetSize(); index++) {
    210       DictionaryValue* printer_data = NULL;
    211       if (printer_list->GetDictionary(index, &printer_data)) {
    212         std::string printer_name;
    213         printer_data->GetString(kNameValue, &printer_name);
    214         std::string printer_id;
    215         printer_data->GetString(kIdValue, &printer_id);
    216 
    217         if (!settings_.ShouldConnect(printer_name)) {
    218           VLOG(1) << "CP_CONNECTOR: Deleting " << printer_name <<
    219               " id: " << printer_id << " as blacklisted";
    220           AddPendingDeleteTask(printer_id);
    221         } else if (RemovePrinterFromList(printer_name, &local_printers)) {
    222           InitJobHandlerForPrinter(printer_data);
    223         } else {
    224           // Cloud printer is not found on the local system.
    225           if (full_list || settings_.delete_on_enum_fail()) {
    226             // Delete if we get the full list of printers or
    227             // |delete_on_enum_fail_| is set.
    228             VLOG(1) << "CP_CONNECTOR: Deleting " << printer_name <<
    229                 " id: " << printer_id <<
    230                 " full_list: " << full_list <<
    231                 " delete_on_enum_fail: " << settings_.delete_on_enum_fail();
    232             AddPendingDeleteTask(printer_id);
    233           } else {
    234             LOG(ERROR) << "CP_CONNECTOR: Printer: " << printer_name <<
    235                 " id: " << printer_id <<
    236                 " not found in print system and full printer list was" <<
    237                 " not received.  Printer will not be able to process" <<
    238                 " jobs.";
    239           }
    240         }
    241       } else {
    242         NOTREACHED();
    243       }
    244     }
    245   }
    246 
    247   request_ = NULL;
    248 
    249   RegisterPrinters(local_printers);
    250   ContinuePendingTaskProcessing();  // Continue processing background tasks.
    251   return CloudPrintURLFetcher::STOP_PROCESSING;
    252 }
    253 
    254 CloudPrintURLFetcher::ResponseAction
    255 CloudPrintConnector::HandlePrinterDeleteResponse(
    256     const net::URLFetcher* source,
    257     const GURL& url,
    258     DictionaryValue* json_data,
    259     bool succeeded) {
    260   VLOG(1) << "CP_CONNECTOR: Handler printer delete response"
    261           << ", succeeded: " << succeeded
    262           << ", url: " << url;
    263   ContinuePendingTaskProcessing();  // Continue processing background tasks.
    264   return CloudPrintURLFetcher::STOP_PROCESSING;
    265 }
    266 
    267 CloudPrintURLFetcher::ResponseAction
    268 CloudPrintConnector::HandleRegisterPrinterResponse(
    269     const net::URLFetcher* source,
    270     const GURL& url,
    271     DictionaryValue* json_data,
    272     bool succeeded) {
    273   VLOG(1) << "CP_CONNECTOR: Handler printer register response"
    274           << ", succeeded: " << succeeded
    275           << ", url: " << url;
    276   if (succeeded) {
    277     ListValue* printer_list = NULL;
    278     // There should be a "printers" value in the JSON
    279     if (json_data->GetList(kPrinterListValue, &printer_list)) {
    280       DictionaryValue* printer_data = NULL;
    281       if (printer_list->GetDictionary(0, &printer_data))
    282         InitJobHandlerForPrinter(printer_data);
    283     }
    284   }
    285   ContinuePendingTaskProcessing();  // Continue processing background tasks.
    286   return CloudPrintURLFetcher::STOP_PROCESSING;
    287 }
    288 
    289 
    290 void CloudPrintConnector::StartGetRequest(const GURL& url,
    291                                           int max_retries,
    292                                           ResponseHandler handler) {
    293   next_response_handler_ = handler;
    294   request_ = CloudPrintURLFetcher::Create();
    295   request_->StartGetRequest(url, this, max_retries, std::string());
    296 }
    297 
    298 void CloudPrintConnector::StartPostRequest(const GURL& url,
    299                                            int max_retries,
    300                                            const std::string& mime_type,
    301                                            const std::string& post_data,
    302                                            ResponseHandler handler) {
    303   next_response_handler_ = handler;
    304   request_ = CloudPrintURLFetcher::Create();
    305   request_->StartPostRequest(
    306       url, this, max_retries, mime_type, post_data, std::string());
    307 }
    308 
    309 void CloudPrintConnector::ReportUserMessage(const std::string& message_id,
    310                                             const std::string& failure_msg) {
    311   // This is a fire and forget type of function.
    312   // Result of this request will be ignored.
    313   std::string mime_boundary;
    314   CreateMimeBoundaryForUpload(&mime_boundary);
    315   GURL url = GetUrlForUserMessage(settings_.server_url(), message_id);
    316   std::string post_data;
    317   net::AddMultipartValueForUpload(kMessageTextValue, failure_msg, mime_boundary,
    318                                   std::string(), &post_data);
    319   net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data);
    320   std::string mime_type("multipart/form-data; boundary=");
    321   mime_type += mime_boundary;
    322   user_message_request_ = CloudPrintURLFetcher::Create();
    323   user_message_request_->StartPostRequest(url, this, 1, mime_type, post_data,
    324                                           std::string());
    325 }
    326 
    327 bool CloudPrintConnector::RemovePrinterFromList(
    328     const std::string& printer_name,
    329     printing::PrinterList* printer_list) {
    330   for (printing::PrinterList::iterator index = printer_list->begin();
    331        index != printer_list->end(); index++) {
    332     if (IsSamePrinter(index->printer_name, printer_name)) {
    333       index = printer_list->erase(index);
    334       return true;
    335     }
    336   }
    337   return false;
    338 }
    339 
    340 void CloudPrintConnector::InitJobHandlerForPrinter(
    341     DictionaryValue* printer_data) {
    342   DCHECK(printer_data);
    343   PrinterJobHandler::PrinterInfoFromCloud printer_info_cloud;
    344   printer_data->GetString(kIdValue, &printer_info_cloud.printer_id);
    345   DCHECK(!printer_info_cloud.printer_id.empty());
    346   VLOG(1) << "CP_CONNECTOR: Init job handler"
    347           << ", printer id: " << printer_info_cloud.printer_id;
    348   JobHandlerMap::iterator index = job_handler_map_.find(
    349       printer_info_cloud.printer_id);
    350   if (index != job_handler_map_.end())
    351     return;  // Nothing to do if we already have a job handler for this printer.
    352 
    353   printing::PrinterBasicInfo printer_info;
    354   printer_data->GetString(kNameValue, &printer_info.printer_name);
    355   DCHECK(!printer_info.printer_name.empty());
    356   printer_data->GetString(kPrinterDescValue,
    357                           &printer_info.printer_description);
    358   // Printer status is a string value which actually contains an integer.
    359   std::string printer_status;
    360   if (printer_data->GetString(kPrinterStatusValue, &printer_status)) {
    361     base::StringToInt(printer_status, &printer_info.printer_status);
    362   }
    363   printer_data->GetString(kPrinterCapsHashValue,
    364       &printer_info_cloud.caps_hash);
    365   ListValue* tags_list = NULL;
    366   if (printer_data->GetList(kTagsValue, &tags_list) && tags_list) {
    367     for (size_t index = 0; index < tags_list->GetSize(); index++) {
    368       std::string tag;
    369       if (tags_list->GetString(index, &tag) &&
    370           StartsWithASCII(tag, kCloudPrintServiceTagsHashTagName, false)) {
    371         std::vector<std::string> tag_parts;
    372         base::SplitStringDontTrim(tag, '=', &tag_parts);
    373         DCHECK_EQ(tag_parts.size(), 2U);
    374         if (tag_parts.size() == 2)
    375           printer_info_cloud.tags_hash = tag_parts[1];
    376       }
    377     }
    378   }
    379   scoped_refptr<PrinterJobHandler> job_handler;
    380   job_handler = new PrinterJobHandler(printer_info,
    381                                       printer_info_cloud,
    382                                       settings_.server_url(),
    383                                       print_system_.get(),
    384                                       this);
    385   job_handler_map_[printer_info_cloud.printer_id] = job_handler;
    386   job_handler->Initialize();
    387 }
    388 
    389 void CloudPrintConnector::AddPendingAvailableTask() {
    390   PendingTask task;
    391   task.type = PENDING_PRINTERS_AVAILABLE;
    392   AddPendingTask(task);
    393 }
    394 
    395 void CloudPrintConnector::AddPendingDeleteTask(const std::string& id) {
    396   PendingTask task;
    397   task.type = PENDING_PRINTER_DELETE;
    398   task.printer_id = id;
    399   AddPendingTask(task);
    400 }
    401 
    402 void CloudPrintConnector::AddPendingRegisterTask(
    403     const printing::PrinterBasicInfo& info) {
    404   PendingTask task;
    405   task.type = PENDING_PRINTER_REGISTER;
    406   task.printer_info = info;
    407   AddPendingTask(task);
    408 }
    409 
    410 void CloudPrintConnector::AddPendingTask(const PendingTask& task) {
    411   pending_tasks_.push_back(task);
    412   // If this is the only pending task, we need to start the process.
    413   if (pending_tasks_.size() == 1) {
    414     base::MessageLoop::current()->PostTask(
    415         FROM_HERE, base::Bind(&CloudPrintConnector::ProcessPendingTask, this));
    416   }
    417 }
    418 
    419 void CloudPrintConnector::ProcessPendingTask() {
    420   if (!IsRunning())
    421     return;  // Orphant call.
    422   if (pending_tasks_.size() == 0)
    423     return;  // No peding tasks.
    424 
    425   PendingTask task = pending_tasks_.front();
    426 
    427   switch (task.type) {
    428     case PENDING_PRINTERS_AVAILABLE :
    429       OnPrintersAvailable();
    430       break;
    431     case PENDING_PRINTER_REGISTER :
    432       OnPrinterRegister(task.printer_info);
    433       break;
    434     case PENDING_PRINTER_DELETE :
    435       OnPrinterDelete(task.printer_id);
    436       break;
    437     default:
    438       NOTREACHED();
    439   }
    440 }
    441 
    442 void CloudPrintConnector::ContinuePendingTaskProcessing() {
    443   if (pending_tasks_.size() == 0)
    444     return;  // No pending tasks.
    445 
    446   // Delete current task and repost if we have more task available.
    447   pending_tasks_.pop_front();
    448   if (pending_tasks_.size() != 0) {
    449     base::MessageLoop::current()->PostTask(
    450         FROM_HERE, base::Bind(&CloudPrintConnector::ProcessPendingTask, this));
    451   }
    452 }
    453 
    454 void CloudPrintConnector::OnPrintersAvailable() {
    455   GURL printer_list_url = GetUrlForPrinterList(
    456       settings_.server_url(), settings_.proxy_id());
    457   StartGetRequest(printer_list_url,
    458                   kCloudPrintRegisterMaxRetryCount,
    459                   &CloudPrintConnector::HandlePrinterListResponse);
    460 }
    461 
    462 void CloudPrintConnector::OnPrinterRegister(
    463     const printing::PrinterBasicInfo& info) {
    464   for (JobHandlerMap::iterator it = job_handler_map_.begin();
    465        it != job_handler_map_.end(); ++it) {
    466     if (IsSamePrinter(it->second->GetPrinterName(), info.printer_name)) {
    467       // Printer already registered, continue to the next task.
    468       ContinuePendingTaskProcessing();
    469       return;
    470     }
    471   }
    472 
    473   // Asynchronously fetch the printer caps and defaults. The story will
    474   // continue in OnReceivePrinterCaps.
    475   print_system_->GetPrinterCapsAndDefaults(
    476       info.printer_name.c_str(),
    477       base::Bind(&CloudPrintConnector::OnReceivePrinterCaps,
    478                  base::Unretained(this)));
    479 }
    480 
    481 void CloudPrintConnector::OnPrinterDelete(const std::string& printer_id) {
    482   // Remove corresponding printer job handler.
    483   JobHandlerMap::iterator it = job_handler_map_.find(printer_id);
    484   if (it != job_handler_map_.end()) {
    485     it->second->Shutdown();
    486     job_handler_map_.erase(it);
    487   }
    488 
    489   // TODO(gene): We probably should not try indefinitely here. Just once or
    490   // twice should be enough.
    491   // Bug: http://code.google.com/p/chromium/issues/detail?id=101850
    492   GURL url = GetUrlForPrinterDelete(
    493       settings_.server_url(), printer_id, "printer_deleted");
    494   StartGetRequest(url,
    495                   kCloudPrintAPIMaxRetryCount,
    496                   &CloudPrintConnector::HandlePrinterDeleteResponse);
    497 }
    498 
    499 void CloudPrintConnector::OnReceivePrinterCaps(
    500     bool succeeded,
    501     const std::string& printer_name,
    502     const printing::PrinterCapsAndDefaults& caps_and_defaults) {
    503   if (!IsRunning())
    504     return;  // Orphant call.
    505   DCHECK(pending_tasks_.size() > 0 &&
    506          pending_tasks_.front().type == PENDING_PRINTER_REGISTER);
    507 
    508   if (!succeeded) {
    509     LOG(ERROR) << "CP_CONNECTOR: Failed to get printer info"
    510                << ", printer name: " << printer_name;
    511     // This printer failed to register, notify the server of this failure.
    512     string16 printer_name_utf16 = UTF8ToUTF16(printer_name);
    513     std::string status_message = l10n_util::GetStringFUTF8(
    514         IDS_CLOUD_PRINT_REGISTER_PRINTER_FAILED,
    515         printer_name_utf16,
    516         l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT));
    517     ReportUserMessage(kGetPrinterCapsFailedMessageId, status_message);
    518 
    519     ContinuePendingTaskProcessing();  // Skip this printer registration.
    520     return;
    521   }
    522 
    523   const printing::PrinterBasicInfo& info = pending_tasks_.front().printer_info;
    524   DCHECK(IsSamePrinter(info.printer_name, printer_name));
    525 
    526   std::string mime_boundary;
    527   CreateMimeBoundaryForUpload(&mime_boundary);
    528   std::string post_data;
    529 
    530   net::AddMultipartValueForUpload(kProxyIdValue,
    531       settings_.proxy_id(), mime_boundary, std::string(), &post_data);
    532   net::AddMultipartValueForUpload(kPrinterNameValue,
    533       info.printer_name, mime_boundary, std::string(), &post_data);
    534   net::AddMultipartValueForUpload(kPrinterDescValue,
    535       info.printer_description, mime_boundary, std::string(), &post_data);
    536   net::AddMultipartValueForUpload(kPrinterStatusValue,
    537       base::StringPrintf("%d", info.printer_status),
    538       mime_boundary, std::string(), &post_data);
    539   post_data += GetPostDataForPrinterInfo(info, mime_boundary);
    540   net::AddMultipartValueForUpload(kPrinterCapsValue,
    541       caps_and_defaults.printer_capabilities, mime_boundary,
    542       caps_and_defaults.caps_mime_type, &post_data);
    543   net::AddMultipartValueForUpload(kPrinterDefaultsValue,
    544       caps_and_defaults.printer_defaults, mime_boundary,
    545       caps_and_defaults.defaults_mime_type, &post_data);
    546   // Send a hash of the printer capabilities to the server. We will use this
    547   // later to check if the capabilities have changed
    548   net::AddMultipartValueForUpload(kPrinterCapsHashValue,
    549       base::MD5String(caps_and_defaults.printer_capabilities),
    550       mime_boundary, std::string(), &post_data);
    551   net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data);
    552   std::string mime_type("multipart/form-data; boundary=");
    553   mime_type += mime_boundary;
    554 
    555   GURL post_url = GetUrlForPrinterRegistration(settings_.server_url());
    556   StartPostRequest(post_url,
    557                    kCloudPrintAPIMaxRetryCount,
    558                    mime_type,
    559                    post_data,
    560                    &CloudPrintConnector::HandleRegisterPrinterResponse);
    561 }
    562 
    563 bool CloudPrintConnector::IsSamePrinter(const std::string& name1,
    564                                         const std::string& name2) const {
    565   return (0 == base::strcasecmp(name1.c_str(), name2.c_str()));
    566 }
    567 
    568 }  // namespace cloud_print
    569