OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2010 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/browser/printing/cloud_print/cloud_print_proxy_backend.h" |
| 6 |
| 7 #include "base/file_util.h" |
| 8 #include "base/md5.h" |
| 9 #include "base/string_util.h" |
| 10 #include "base/utf_string_conversions.h" |
| 11 #include "base/values.h" |
| 12 #include "chrome/browser/profile.h" |
| 13 #include "chrome/browser/printing/cloud_print/cloud_print_consts.h" |
| 14 #include "chrome/browser/printing/cloud_print/cloud_print_helpers.h" |
| 15 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" |
| 16 #include "chrome/browser/printing/cloud_print/printer_job_handler.h" |
| 17 #include "googleurl/src/gurl.h" |
| 18 #include "net/url_request/url_request_status.h" |
| 19 |
| 20 // The real guts of SyncBackendHost, to keep the public client API clean. |
| 21 class CloudPrintProxyBackend::Core |
| 22 : public base::RefCountedThreadSafe<CloudPrintProxyBackend::Core>, |
| 23 public URLFetcherDelegate, |
| 24 public cloud_print::PrinterChangeNotifierDelegate, |
| 25 public PrinterJobHandlerDelegate { |
| 26 public: |
| 27 explicit Core(CloudPrintProxyBackend* backend); |
| 28 // Note: |
| 29 // |
| 30 // The Do* methods are the various entry points from CloudPrintProxyBackend |
| 31 // It calls us on a dedicated thread to actually perform synchronous |
| 32 // (and potentially blocking) syncapi operations. |
| 33 // |
| 34 // Called on the CloudPrintProxyBackend core_thread_ to perform |
| 35 // initialization |
| 36 void DoInitialize(const std::string& auth_token, |
| 37 const std::string& proxy_id); |
| 38 // Called on the CloudPrintProxyBackend core_thread_ to perform |
| 39 // shutdown. |
| 40 void DoShutdown(); |
| 41 void DoRegisterSelectedPrinters( |
| 42 const cloud_print::PrinterList& printer_list); |
| 43 void DoHandlePrinterNotification(const std::string& printer_id); |
| 44 |
| 45 // URLFetcher::Delegate implementation. |
| 46 virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, |
| 47 const URLRequestStatus& status, |
| 48 int response_code, |
| 49 const ResponseCookies& cookies, |
| 50 const std::string& data); |
| 51 // cloud_print::PrinterChangeNotifier::Delegate implementation |
| 52 virtual void OnPrinterAdded(); |
| 53 virtual void OnPrinterDeleted() { |
| 54 } |
| 55 virtual void OnPrinterChanged() { |
| 56 } |
| 57 virtual void OnJobChanged() { |
| 58 } |
| 59 // PrinterJobHandler::Delegate implementation |
| 60 void OnPrinterJobHandlerShutdown(PrinterJobHandler* job_handler, |
| 61 const std::string& printer_id); |
| 62 |
| 63 protected: |
| 64 // FrontendNotification defines parameters for NotifyFrontend. Each enum |
| 65 // value corresponds to the one CloudPrintProcyService method that |
| 66 // NotifyFrontend should invoke. |
| 67 enum FrontendNotification { |
| 68 PRINTER_LIST_AVAILABLE, // OnPrinterListAvailable |
| 69 }; |
| 70 // Prototype for a response handler. |
| 71 typedef void (CloudPrintProxyBackend::Core::*ResponseHandler)( |
| 72 const URLFetcher* source, const GURL& url, |
| 73 const URLRequestStatus& status, int response_code, |
| 74 const ResponseCookies& cookies, const std::string& data); |
| 75 // Begin response handlers |
| 76 void HandlePrinterListResponse(const URLFetcher* source, const GURL& url, |
| 77 const URLRequestStatus& status, |
| 78 int response_code, |
| 79 const ResponseCookies& cookies, |
| 80 const std::string& data); |
| 81 void HandleRegisterPrinterResponse(const URLFetcher* source, |
| 82 const GURL& url, |
| 83 const URLRequestStatus& status, |
| 84 int response_code, |
| 85 const ResponseCookies& cookies, |
| 86 const std::string& data); |
| 87 // End response handlers |
| 88 |
| 89 // NotifyFrontend is how the Core communicates with the frontend across |
| 90 // threads. |
| 91 void NotifyFrontend(FrontendNotification notification); |
| 92 // Starts a new printer registration process. |
| 93 void StartRegistration(); |
| 94 // Ends the printer registration process. |
| 95 void EndRegistration(); |
| 96 // Registers printer capabilities and defaults for the next printer in the |
| 97 // list with the cloud print server. |
| 98 void RegisterNextPrinter(); |
| 99 // Retrieves the list of registered printers for this user/proxy combination |
| 100 // from the cloud print server. |
| 101 void GetRegisteredPrinters(); |
| 102 void HandleServerError(Task* task_to_retry); |
| 103 // Removes the given printer from the list. Returns false if the printer |
| 104 // did not exist in the list. |
| 105 bool RemovePrinterFromList(const std::string& printer_name); |
| 106 // Initializes a job handler object for the specified printer. The job |
| 107 // handler is responsible for checking for pending print jobs for this |
| 108 // printer and print them. |
| 109 void InitJobHandlerForPrinter(DictionaryValue* printer_data); |
| 110 |
| 111 // Our parent CloudPrintProxyBackend |
| 112 CloudPrintProxyBackend* backend_; |
| 113 // The list of printers to be registered with the cloud print server. |
| 114 // To begin with,this list is initialized with the list of local and network |
| 115 // printers available. Then we query the server for the list of printers |
| 116 // already registered. We trim this list to remove the printers already |
| 117 // registered. We then pass a copy of this list to the frontend to give the |
| 118 // user a chance to further trim the list. When the frontend gives us the |
| 119 // final list we make a copy into this so that we can start registering. |
| 120 cloud_print::PrinterList printer_list_; |
| 121 // The URLFetcher instance for the current request |
| 122 scoped_ptr<URLFetcher> request_; |
| 123 // The index of the nex printer to be uploaded. |
| 124 size_t next_upload_index_; |
| 125 // The unique id for this proxy |
| 126 std::string proxy_id_; |
| 127 // The GAIA auth token |
| 128 std::string auth_token_; |
| 129 // The number of consecutive times that connecting to the server failed. |
| 130 int server_error_count_; |
| 131 // Cached info about the last printer that we tried to upload. We cache this |
| 132 // so we won't have to requery the printer if the upload fails and we need |
| 133 // to retry. |
| 134 std::string last_uploaded_printer_name_; |
| 135 cloud_print::PrinterCapsAndDefaults last_uploaded_printer_info_; |
| 136 // A map of printer id to job handler. |
| 137 typedef std::map<std::string, scoped_refptr<PrinterJobHandler> > |
| 138 JobHandlerMap; |
| 139 JobHandlerMap job_handler_map_; |
| 140 ResponseHandler next_response_handler_; |
| 141 cloud_print::PrinterChangeNotifier printer_change_notifier_; |
| 142 bool new_printers_available_; |
| 143 |
| 144 DISALLOW_COPY_AND_ASSIGN(Core); |
| 145 }; |
| 146 |
| 147 CloudPrintProxyBackend::CloudPrintProxyBackend( |
| 148 CloudPrintProxyFrontend* frontend) |
| 149 : core_thread_("Chrome_CloudPrintProxyCoreThread"), |
| 150 frontend_loop_(MessageLoop::current()), |
| 151 frontend_(frontend) { |
| 152 DCHECK(frontend_); |
| 153 core_ = new Core(this); |
| 154 } |
| 155 |
| 156 CloudPrintProxyBackend::~CloudPrintProxyBackend() { |
| 157 DCHECK(!core_); |
| 158 } |
| 159 |
| 160 bool CloudPrintProxyBackend::Initialize(const std::string& auth_token, |
| 161 const std::string& proxy_id) { |
| 162 if (!core_thread_.Start()) |
| 163 return false; |
| 164 core_thread_.message_loop()->PostTask(FROM_HERE, |
| 165 NewRunnableMethod( |
| 166 core_.get(), &CloudPrintProxyBackend::Core::DoInitialize, auth_token, |
| 167 proxy_id)); |
| 168 return true; |
| 169 } |
| 170 |
| 171 void CloudPrintProxyBackend::Shutdown() { |
| 172 core_thread_.message_loop()->PostTask(FROM_HERE, |
| 173 NewRunnableMethod(core_.get(), |
| 174 &CloudPrintProxyBackend::Core::DoShutdown)); |
| 175 core_thread_.Stop(); |
| 176 core_ = NULL; // Releases reference to core_. |
| 177 } |
| 178 |
| 179 void CloudPrintProxyBackend::RegisterPrinters( |
| 180 const cloud_print::PrinterList& printer_list) { |
| 181 core_thread_.message_loop()->PostTask(FROM_HERE, |
| 182 NewRunnableMethod( |
| 183 core_.get(), |
| 184 &CloudPrintProxyBackend::Core::DoRegisterSelectedPrinters, |
| 185 printer_list)); |
| 186 } |
| 187 |
| 188 void CloudPrintProxyBackend::HandlePrinterNotification( |
| 189 const std::string& printer_id) { |
| 190 core_thread_.message_loop()->PostTask(FROM_HERE, |
| 191 NewRunnableMethod( |
| 192 core_.get(), |
| 193 &CloudPrintProxyBackend::Core::DoHandlePrinterNotification, |
| 194 printer_id)); |
| 195 } |
| 196 |
| 197 CloudPrintProxyBackend::Core::Core(CloudPrintProxyBackend* backend) |
| 198 : backend_(backend), next_upload_index_(0), server_error_count_(0), |
| 199 next_response_handler_(NULL), new_printers_available_(false) { |
| 200 } |
| 201 |
| 202 void CloudPrintProxyBackend::Core::DoInitialize(const std::string& auth_token, |
| 203 const std::string& proxy_id) { |
| 204 DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop()); |
| 205 printer_change_notifier_.StartWatching(std::string(), this); |
| 206 proxy_id_ = proxy_id; |
| 207 auth_token_ = auth_token; |
| 208 StartRegistration(); |
| 209 } |
| 210 |
| 211 void CloudPrintProxyBackend::Core::StartRegistration() { |
| 212 printer_list_.clear(); |
| 213 cloud_print::EnumeratePrinters(&printer_list_); |
| 214 server_error_count_ = 0; |
| 215 // Now we need to ask the server about printers that were registered on the |
| 216 // server so that we can trim this list. |
| 217 GetRegisteredPrinters(); |
| 218 } |
| 219 |
| 220 void CloudPrintProxyBackend::Core::EndRegistration() { |
| 221 request_.reset(); |
| 222 if (new_printers_available_) { |
| 223 new_printers_available_ = false; |
| 224 StartRegistration(); |
| 225 } |
| 226 } |
| 227 |
| 228 void CloudPrintProxyBackend::Core::DoShutdown() { |
| 229 // Need to kill all running jobs. |
| 230 while (!job_handler_map_.empty()) { |
| 231 JobHandlerMap::iterator index = job_handler_map_.begin(); |
| 232 // Shutdown will call our OnPrinterJobHandlerShutdown method which will |
| 233 // remove this from the map. |
| 234 index->second->Shutdown(); |
| 235 } |
| 236 } |
| 237 |
| 238 void CloudPrintProxyBackend::Core::DoRegisterSelectedPrinters( |
| 239 const cloud_print::PrinterList& printer_list) { |
| 240 server_error_count_ = 0; |
| 241 printer_list_.assign(printer_list.begin(), printer_list.end()); |
| 242 DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop()); |
| 243 next_upload_index_ = 0; |
| 244 RegisterNextPrinter(); |
| 245 } |
| 246 |
| 247 void CloudPrintProxyBackend::Core::DoHandlePrinterNotification( |
| 248 const std::string& printer_id) { |
| 249 JobHandlerMap::iterator index = job_handler_map_.find(printer_id); |
| 250 if (index != job_handler_map_.end()) |
| 251 index->second->NotifyJobAvailable(); |
| 252 } |
| 253 |
| 254 void CloudPrintProxyBackend::Core::GetRegisteredPrinters() { |
| 255 request_.reset( |
| 256 new URLFetcher(CloudPrintHelpers::GetUrlForPrinterList(proxy_id_), |
| 257 URLFetcher::GET, this)); |
| 258 CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); |
| 259 next_response_handler_ = |
| 260 &CloudPrintProxyBackend::Core::HandlePrinterListResponse; |
| 261 request_->Start(); |
| 262 } |
| 263 |
| 264 void CloudPrintProxyBackend::Core::RegisterNextPrinter() { |
| 265 // For the next printer to be uploaded, create a multi-part post request to |
| 266 // upload the printer capabilities and the printer defaults. |
| 267 if (next_upload_index_ < printer_list_.size()) { |
| 268 const cloud_print::PrinterBasicInfo& info = |
| 269 printer_list_.at(next_upload_index_); |
| 270 bool have_printer_info = true; |
| 271 // If we are retrying a previous upload, we don't need to fetch the caps |
| 272 // and defaults again. |
| 273 if (info.printer_name != last_uploaded_printer_name_) { |
| 274 have_printer_info = cloud_print::GetPrinterCapsAndDefaults( |
| 275 info.printer_name.c_str(), &last_uploaded_printer_info_); |
| 276 } |
| 277 if (have_printer_info) { |
| 278 last_uploaded_printer_name_ = info.printer_name; |
| 279 std::string mime_boundary; |
| 280 CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); |
| 281 std::string post_data; |
| 282 CloudPrintHelpers::AddMultipartValueForUpload(kProxyIdValue, proxy_id_, |
| 283 mime_boundary, |
| 284 std::string(), &post_data); |
| 285 CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue, |
| 286 info.printer_name, |
| 287 mime_boundary, |
| 288 std::string(), &post_data); |
| 289 CloudPrintHelpers::AddMultipartValueForUpload(kPrinterDescValue, |
| 290 info.printer_description, |
| 291 mime_boundary, |
| 292 std::string() , &post_data); |
| 293 CloudPrintHelpers::AddMultipartValueForUpload( |
| 294 kPrinterStatusValue, StringPrintf("%d", info.printer_status), |
| 295 mime_boundary, std::string(), &post_data); |
| 296 CloudPrintHelpers::AddMultipartValueForUpload( |
| 297 kPrinterCapsValue, last_uploaded_printer_info_.printer_capabilities, |
| 298 mime_boundary, last_uploaded_printer_info_.caps_mime_type, |
| 299 &post_data); |
| 300 CloudPrintHelpers::AddMultipartValueForUpload( |
| 301 kPrinterDefaultsValue, last_uploaded_printer_info_.printer_defaults, |
| 302 mime_boundary, last_uploaded_printer_info_.defaults_mime_type, |
| 303 &post_data); |
| 304 // Send a hash of the printer capabilities to the server. We will use this |
| 305 // later to check if the capabilities have changed |
| 306 CloudPrintHelpers::AddMultipartValueForUpload( |
| 307 WideToUTF8(kPrinterCapsHashValue).c_str(), |
| 308 MD5String(last_uploaded_printer_info_.printer_capabilities), |
| 309 mime_boundary, std::string(), &post_data); |
| 310 // Terminate the request body |
| 311 post_data.append("--" + mime_boundary + "--\r\n"); |
| 312 std::string mime_type("multipart/form-data; boundary="); |
| 313 mime_type += mime_boundary; |
| 314 request_.reset( |
| 315 new URLFetcher(CloudPrintHelpers::GetUrlForPrinterRegistration(), |
| 316 URLFetcher::POST, this)); |
| 317 CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); |
| 318 request_->set_upload_data(mime_type, post_data); |
| 319 next_response_handler_ = |
| 320 &CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse; |
| 321 request_->Start(); |
| 322 } else { |
| 323 NOTREACHED(); |
| 324 } |
| 325 } else { |
| 326 EndRegistration(); |
| 327 } |
| 328 } |
| 329 |
| 330 // URLFetcher::Delegate implementation. |
| 331 void CloudPrintProxyBackend::Core::OnURLFetchComplete( |
| 332 const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
| 333 int response_code, const ResponseCookies& cookies, |
| 334 const std::string& data) { |
| 335 DCHECK(source == request_.get()); |
| 336 // We need a next response handler |
| 337 DCHECK(next_response_handler_); |
| 338 (this->*next_response_handler_)(source, url, status, response_code, |
| 339 cookies, data); |
| 340 } |
| 341 |
| 342 void CloudPrintProxyBackend::Core::NotifyFrontend( |
| 343 FrontendNotification notification) { |
| 344 switch (notification) { |
| 345 case PRINTER_LIST_AVAILABLE: |
| 346 backend_->frontend_->OnPrinterListAvailable(printer_list_); |
| 347 break; |
| 348 default: |
| 349 NOTREACHED(); |
| 350 break; |
| 351 } |
| 352 } |
| 353 |
| 354 void CloudPrintProxyBackend::Core::HandlePrinterListResponse( |
| 355 const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
| 356 int response_code, const ResponseCookies& cookies, |
| 357 const std::string& data) { |
| 358 if (status.is_success()) { |
| 359 server_error_count_ = 0; |
| 360 if (response_code == 200) { |
| 361 // Parse the response JSON for the list of printers already registered. |
| 362 bool succeeded = false; |
| 363 DictionaryValue* response_dict_temp = NULL; |
| 364 CloudPrintHelpers::ParseResponseJSON(data, &succeeded, |
| 365 &response_dict_temp); |
| 366 scoped_ptr<DictionaryValue> response_dict; |
| 367 response_dict.reset(response_dict_temp); |
| 368 if (succeeded) { |
| 369 DCHECK(response_dict.get()); |
| 370 ListValue* printer_list = NULL; |
| 371 response_dict->GetList(kPrinterListValue, &printer_list); |
| 372 // There may be no "printers" value in the JSON |
| 373 if (printer_list) { |
| 374 for (size_t index = 0; index < printer_list->GetSize(); index++) { |
| 375 DictionaryValue* printer_data = NULL; |
| 376 if (printer_list->GetDictionary(index, &printer_data)) { |
| 377 std::string printer_name; |
| 378 printer_data->GetString(kNameValue, &printer_name); |
| 379 RemovePrinterFromList(printer_name); |
| 380 InitJobHandlerForPrinter(printer_data); |
| 381 } else { |
| 382 NOTREACHED(); |
| 383 } |
| 384 } |
| 385 } |
| 386 } |
| 387 } |
| 388 MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); |
| 389 if (!printer_list_.empty()) { |
| 390 // Let the frontend know that we have a list of printers available. |
| 391 backend_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| 392 &Core::NotifyFrontend, PRINTER_LIST_AVAILABLE)); |
| 393 } else { |
| 394 // No more work to be done here. |
| 395 MessageLoop::current()->PostTask( |
| 396 FROM_HERE, NewRunnableMethod(this, &Core::EndRegistration)); |
| 397 } |
| 398 } else { |
| 399 HandleServerError(NewRunnableMethod(this, &Core::GetRegisteredPrinters)); |
| 400 } |
| 401 } |
| 402 |
| 403 void CloudPrintProxyBackend::Core::InitJobHandlerForPrinter( |
| 404 DictionaryValue* printer_data) { |
| 405 DCHECK(printer_data); |
| 406 std::string printer_id; |
| 407 printer_data->GetString(kIdValue, &printer_id); |
| 408 DCHECK(!printer_id.empty()); |
| 409 JobHandlerMap::iterator index = job_handler_map_.find(printer_id); |
| 410 // We might already have a job handler for this printer |
| 411 if (index == job_handler_map_.end()) { |
| 412 cloud_print::PrinterBasicInfo printer_info; |
| 413 printer_data->GetString(kNameValue, &printer_info.printer_name); |
| 414 DCHECK(!printer_info.printer_name.empty()); |
| 415 printer_data->GetString(UTF8ToWide(kPrinterDescValue), |
| 416 &printer_info.printer_description); |
| 417 printer_data->GetInteger(UTF8ToWide(kPrinterStatusValue), |
| 418 &printer_info.printer_status); |
| 419 std::string caps_hash; |
| 420 printer_data->GetString(kPrinterCapsHashValue, &caps_hash); |
| 421 scoped_refptr<PrinterJobHandler> job_handler; |
| 422 job_handler = new PrinterJobHandler(printer_info, printer_id, caps_hash, |
| 423 auth_token_, this); |
| 424 job_handler_map_[printer_id] = job_handler; |
| 425 job_handler->Initialize(); |
| 426 } |
| 427 } |
| 428 |
| 429 void CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse( |
| 430 const URLFetcher* source, const GURL& url, const URLRequestStatus& status, |
| 431 int response_code, const ResponseCookies& cookies, |
| 432 const std::string& data) { |
| 433 Task* next_task = |
| 434 NewRunnableMethod(this, |
| 435 &CloudPrintProxyBackend::Core::RegisterNextPrinter); |
| 436 if (status.is_success() && (response_code == 200)) { |
| 437 bool succeeded = false; |
| 438 DictionaryValue* response_dict = NULL; |
| 439 CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); |
| 440 if (succeeded) { |
| 441 DCHECK(response_dict); |
| 442 ListValue* printer_list = NULL; |
| 443 response_dict->GetList(kPrinterListValue, &printer_list); |
| 444 // There should be a "printers" value in the JSON |
| 445 DCHECK(printer_list); |
| 446 if (printer_list) { |
| 447 DictionaryValue* printer_data = NULL; |
| 448 if (printer_list->GetDictionary(0, &printer_data)) { |
| 449 InitJobHandlerForPrinter(printer_data); |
| 450 } |
| 451 } |
| 452 } |
| 453 server_error_count_ = 0; |
| 454 next_upload_index_++; |
| 455 MessageLoop::current()->PostTask(FROM_HERE, next_task); |
| 456 } else { |
| 457 HandleServerError(next_task); |
| 458 } |
| 459 } |
| 460 |
| 461 void CloudPrintProxyBackend::Core::HandleServerError(Task* task_to_retry) { |
| 462 CloudPrintHelpers::HandleServerError( |
| 463 &server_error_count_, -1, kMaxRetryInterval, kBaseRetryInterval, |
| 464 task_to_retry, NULL); |
| 465 } |
| 466 |
| 467 bool CloudPrintProxyBackend::Core::RemovePrinterFromList( |
| 468 const std::string& printer_name) { |
| 469 bool ret = false; |
| 470 for (cloud_print::PrinterList::iterator index = printer_list_.begin(); |
| 471 index != printer_list_.end(); index++) { |
| 472 if (0 == base::strcasecmp(index->printer_name.c_str(), |
| 473 printer_name.c_str())) { |
| 474 index = printer_list_.erase(index); |
| 475 ret = true; |
| 476 break; |
| 477 } |
| 478 } |
| 479 return ret; |
| 480 } |
| 481 |
| 482 // cloud_print::PrinterChangeNotifier::Delegate implementation |
| 483 void CloudPrintProxyBackend::Core::OnPrinterAdded() { |
| 484 if (request_.get()) { |
| 485 new_printers_available_ = true; |
| 486 } else { |
| 487 StartRegistration(); |
| 488 } |
| 489 } |
| 490 |
| 491 // PrinterJobHandler::Delegate implementation |
| 492 void CloudPrintProxyBackend::Core::OnPrinterJobHandlerShutdown( |
| 493 PrinterJobHandler* job_handler, const std::string& printer_id) { |
| 494 job_handler_map_.erase(printer_id); |
| 495 } |
| 496 |
OLD | NEW |