diff options
author | sanjeevr@google.com <sanjeevr@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-20 02:03:48 +0000 |
---|---|---|
committer | sanjeevr@google.com <sanjeevr@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-20 02:03:48 +0000 |
commit | 1bd056e69770a53c2778c46918e42e2b8eed6de3 (patch) | |
tree | 4534fd75545de891c0a1a3ce549f1ade764e877a | |
parent | 4d19b9e62e2e7ce6eff44e3d37617159361b8050 (diff) | |
download | chromium_src-1bd056e69770a53c2778c46918e42e2b8eed6de3.zip chromium_src-1bd056e69770a53c2778c46918e42e2b8eed6de3.tar.gz chromium_src-1bd056e69770a53c2778c46918e42e2b8eed6de3.tar.bz2 |
First cut of Cloud Print Proxy implementation. The code is not enabled for now. Soon the cloud print proxy code will move from the browser process to a background process called the service process.
BUG=None
TEST=None for now
Review URL: http://codereview.chromium.org/1566047
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@45000 0039d316-1c4b-4281-b951-d872f2087c98
23 files changed, 2827 insertions, 0 deletions
diff --git a/chrome/browser/printing/cloud_print/cloud_print_consts.cc b/chrome/browser/printing/cloud_print/cloud_print_consts.cc new file mode 100644 index 0000000..d1c4685 --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_consts.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2010 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. + +// Constant defines used in the cloud print proxy code + +#include "chrome/browser/printing/cloud_print/cloud_print_consts.h" + +const char kProxyIdValue[] = "proxy"; +const char kPrinterNameValue[] = "printer"; +const char kPrinterDescValue[] = "description"; +const char kPrinterCapsValue[] = "capabilities"; +const char kPrinterDefaultsValue[] = "defaults"; +const char kPrinterStatusValue[] = "status"; + +// Values in the respone JSON from the cloud print server +const wchar_t kPrinterListValue[] = L"printers"; +const wchar_t kSuccessValue[] = L"success"; +const wchar_t kNameValue[] = L"name"; +const wchar_t kIdValue[] = L"id"; +const wchar_t kTicketUrlValue[] = L"ticketUrl"; +const wchar_t kFileUrlValue[] = L"fileUrl"; +const wchar_t kJobListValue[] = L"jobs"; +const wchar_t kTitleValue[] = L"title"; +const wchar_t kPrinterCapsHashValue[] = L"capsHash"; + +// TODO(sanjeevr): Change this to a real one. Also read this from prefs instead +// of hardcoding. +const char kCloudPrintServerUrl[] = "https://<TBD>"; + diff --git a/chrome/browser/printing/cloud_print/cloud_print_consts.h b/chrome/browser/printing/cloud_print/cloud_print_consts.h new file mode 100644 index 0000000..13a091f --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_consts.h @@ -0,0 +1,37 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_CONSTS_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_CONSTS_H_ + +#include "base/basictypes.h" + +// Constant defines used in the cloud print proxy code +extern const char kProxyIdValue[]; +extern const char kPrinterNameValue[]; +extern const char kPrinterDescValue[]; +extern const char kPrinterCapsValue[]; +extern const char kPrinterDefaultsValue[]; +extern const char kPrinterStatusValue[]; +// Values in the respone JSON from the cloud print server +extern const wchar_t kPrinterListValue[]; +extern const wchar_t kSuccessValue[]; +extern const wchar_t kNameValue[]; +extern const wchar_t kIdValue[]; +extern const wchar_t kTicketUrlValue[]; +extern const wchar_t kFileUrlValue[]; +extern const wchar_t kJobListValue[]; +extern const wchar_t kTitleValue[]; +extern const wchar_t kPrinterCapsHashValue[]; + +extern const char kCloudPrintServerUrl[]; + +// Max interval between retrying connection to the server +const int64 kMaxRetryInterval = 5*60*1000; // 5 minutes in millseconds +const int64 kBaseRetryInterval = 5*1000; // 5 seconds +const int kMaxRetryCount = 5; +const int64 kJobStatusUpdateInterval = 10*1000; // 10 seconds + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_CONSTS_H_ + diff --git a/chrome/browser/printing/cloud_print/cloud_print_helpers.cc b/chrome/browser/printing/cloud_print/cloud_print_helpers.cc new file mode 100644 index 0000000..336a203 --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_helpers.cc @@ -0,0 +1,161 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/cloud_print_helpers.h" + +#include "base/json/json_reader.h" +#include "base/rand_util.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/task.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/net/url_fetcher.h" +#include "chrome/browser/printing/cloud_print/cloud_print_consts.h" +#include "chrome/browser/profile.h" + +std::string StringFromJobStatus(cloud_print::PrintJobStatus status) { + std::string ret; + switch (status) { + case cloud_print::PRINT_JOB_STATUS_IN_PROGRESS: + ret = "in_progress"; + break; + case cloud_print::PRINT_JOB_STATUS_ERROR: + ret = "error"; + break; + case cloud_print::PRINT_JOB_STATUS_COMPLETED: + ret = "done"; + break; + default: + ret = "unknown"; + NOTREACHED(); + break; + } + return ret; +} + +GURL CloudPrintHelpers::GetUrlForPrinterRegistration() { + return GURL(StringPrintf("%s/printing/register", kCloudPrintServerUrl)); +} + +GURL CloudPrintHelpers::GetUrlForPrinterUpdate(const std::string& printer_id) { + return GURL(StringPrintf("%s/printing/update?printerid=%s", + kCloudPrintServerUrl, printer_id.c_str())); +} + +GURL CloudPrintHelpers::GetUrlForPrinterDelete(const std::string& printer_id) { + return GURL(StringPrintf("%s/printing/delete?printerid=%s", + kCloudPrintServerUrl, printer_id.c_str())); +} + +GURL CloudPrintHelpers::GetUrlForPrinterList(const std::string& proxy_id) { + return GURL(StringPrintf( + "%s/printing/list?proxy=%s", + kCloudPrintServerUrl, proxy_id.c_str())); +} + +GURL CloudPrintHelpers::GetUrlForJobFetch(const std::string& printer_id) { + return GURL(StringPrintf( + "%s/printing/fetch?printerid=%s", + kCloudPrintServerUrl, printer_id.c_str())); +} + +GURL CloudPrintHelpers::GetUrlForJobStatusUpdate( + const std::string& job_id, cloud_print::PrintJobStatus status) { + std::string status_string = StringFromJobStatus(status); + return GURL(StringPrintf( + "%s/printing/control?jobid=%s&status=%s", + kCloudPrintServerUrl, job_id.c_str(), status_string.c_str())); +} + +GURL CloudPrintHelpers::GetUrlForJobStatusUpdate( + const std::string& job_id, const cloud_print::PrintJobDetails& details) { + std::string status_string = StringFromJobStatus(details.status); + return GURL(StringPrintf( + "%s/printing/control?jobid=%s&status=%s&code=%d&message=%s" + "&numpages=%d&pagesprinted=%d", + kCloudPrintServerUrl, job_id.c_str(), status_string.c_str(), + details.platform_status_flags, details.status_message.c_str(), + details.total_pages, details.pages_printed)); +} + +bool CloudPrintHelpers::ParseResponseJSON( + const std::string& response_data, bool* succeeded, + DictionaryValue** response_dict) { + scoped_ptr<Value> message_value(base::JSONReader::Read(response_data, false)); + DCHECK(message_value.get()); + if (!message_value.get()) { + return false; + } + if (!message_value->IsType(Value::TYPE_DICTIONARY)) { + NOTREACHED(); + return false; + } + DictionaryValue* response_dict_local = + static_cast<DictionaryValue*>(message_value.get()); + if (succeeded) { + response_dict_local->GetBoolean(kSuccessValue, succeeded); + } + if (response_dict) { + *response_dict = response_dict_local; + message_value.release(); + } + return true; +} + +void CloudPrintHelpers::PrepCloudPrintRequest(URLFetcher* request, + const std::string& auth_token) { + request->set_request_context( + Profile::GetDefaultRequestContext()); + std::string headers = "Authorization: GoogleLogin auth="; + headers += auth_token; + request->set_extra_request_headers(headers); +} + +void CloudPrintHelpers::HandleServerError(int* error_count, int max_retry_count, + int64 max_retry_interval, + int64 base_retry_interval, + Task* task_to_retry, + Task* task_on_give_up) { + (*error_count)++; + if ((-1 != max_retry_count) && (*error_count > max_retry_count)) { + if (task_on_give_up) { + MessageLoop::current()->PostTask(FROM_HERE, task_on_give_up); + } + } else { + int64 retry_interval = base_retry_interval * (*error_count); + if ((-1 != max_retry_interval) && (retry_interval > max_retry_interval)) { + retry_interval = max_retry_interval; + } + MessageLoop::current()->PostDelayedTask(FROM_HERE, task_to_retry, + retry_interval); + } +} + +void CloudPrintHelpers::AddMultipartValueForUpload( + const std::string& value_name, const std::string& value, + const std::string& mime_boundary, const std::string& content_type, + std::string* post_data) { + DCHECK(post_data); + // First line is the boundary + post_data->append("--" + mime_boundary + "\r\n"); + // Next line is the Content-disposition + post_data->append(StringPrintf("Content-Disposition: form-data; " + "name=\"%s\"\r\n", value_name.c_str())); + if (!content_type.empty()) { + // If Content-type is specified, the next line is that + post_data->append(StringPrintf("Content-Type: %s\r\n", + content_type.c_str())); + } + // Leave an empty line and append the value. + post_data->append(StringPrintf("\r\n%s\r\n", value.c_str())); +} + +// Create a MIME boundary marker (27 '-' characters followed by 16 hex digits). +void CloudPrintHelpers::CreateMimeBoundaryForUpload(std::string* out) { + int r1 = base::RandInt(0, kint32max); + int r2 = base::RandInt(0, kint32max); + SStringPrintf(out, "---------------------------%08X%08X", r1, r2); +} + diff --git a/chrome/browser/printing/cloud_print/cloud_print_helpers.h b/chrome/browser/printing/cloud_print/cloud_print_helpers.h new file mode 100644 index 0000000..46f0e5b --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_helpers.h @@ -0,0 +1,71 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_ + +#include <string> + +#include "chrome/browser/printing/cloud_print/printer_info.h" +#include "googleurl/src/gurl.h" + +class DictionaryValue; +class Task; +class URLFetcher; + +// Helper methods for the cloud print proxy code. +class CloudPrintHelpers { + public: + static GURL GetUrlForPrinterRegistration(); + static GURL GetUrlForPrinterUpdate(const std::string& printer_id); + static GURL GetUrlForPrinterDelete(const std::string& printer_id); + static GURL GetUrlForPrinterList(const std::string& proxy_id); + static GURL GetUrlForJobFetch(const std::string& printer_id); + static GURL GetUrlForJobStatusUpdate(const std::string& job_id, + cloud_print::PrintJobStatus status); + static GURL GetUrlForJobStatusUpdate( + const std::string& job_id, const cloud_print::PrintJobDetails& details); + // Parses the response data for any cloud print server request. The method + // returns false if there was an error in parsing the JSON. The succeeded + // value returns the value of the "success" value in the response JSON. + // Returns the response as a dictionary value. + static bool ParseResponseJSON(const std::string& response_data, + bool* succeeded, DictionaryValue** response_dict); + // Sets up common parameters for a cloud print request + // (such as the GAIA auth token in the request headers, the request context + // etc). + static void PrepCloudPrintRequest(URLFetcher* request, + const std::string& auth_token); + // Strictly speaking, this helper method is not specific to cloud printing. + // It handles the logic to retry tasks when the server returns an error. + // The parameters are as below: + // |error_count| Contains the current number of consecutive failed attempts. + // This method increments it (in/out) + // |max_retry_count| Number of retries before giving up. -1 implies no limit. + // |max_retry_interval| Maximum amount of time (in ms) we are willing to + // wait between retries. -1 implies no limit. + // |base_retry_interval| Starting value of the retry interval. This + // method progressively increases the interval on each retry. + // |task_to_retry| The task to be retried. + // |task_on_give_up| Task to be performed when we give up. This is only + // valid when max_retry_count is not -1. It can be NULL. + static void HandleServerError(int* error_count, int max_retry_count, + int64 max_retry_interval, + int64 base_retry_interval, + Task* task_to_retry, Task* task_on_give_up); + // Prepares one value as part of a multi-part upload request. + static void AddMultipartValueForUpload( + const std::string& value_name, const std::string& value, + const std::string& mime_boundary, const std::string& content_type, + std::string* post_data); +// Create a MIME boundary marker (27 '-' characters followed by 16 hex digits). + static void CreateMimeBoundaryForUpload(std::string *out); + + private: + CloudPrintHelpers() { + } +}; + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_ + diff --git a/chrome/browser/printing/cloud_print/cloud_print_proxy_backend.cc b/chrome/browser/printing/cloud_print/cloud_print_proxy_backend.cc new file mode 100644 index 0000000..dbc6640 --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_proxy_backend.cc @@ -0,0 +1,496 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/cloud_print_proxy_backend.h" + +#include "base/file_util.h" +#include "base/md5.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/printing/cloud_print/cloud_print_consts.h" +#include "chrome/browser/printing/cloud_print/cloud_print_helpers.h" +#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" +#include "chrome/browser/printing/cloud_print/printer_job_handler.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_request_status.h" + +// The real guts of SyncBackendHost, to keep the public client API clean. +class CloudPrintProxyBackend::Core + : public base::RefCountedThreadSafe<CloudPrintProxyBackend::Core>, + public URLFetcherDelegate, + public cloud_print::PrinterChangeNotifierDelegate, + public PrinterJobHandlerDelegate { + public: + explicit Core(CloudPrintProxyBackend* backend); + // Note: + // + // The Do* methods are the various entry points from CloudPrintProxyBackend + // It calls us on a dedicated thread to actually perform synchronous + // (and potentially blocking) syncapi operations. + // + // Called on the CloudPrintProxyBackend core_thread_ to perform + // initialization + void DoInitialize(const std::string& auth_token, + const std::string& proxy_id); + // Called on the CloudPrintProxyBackend core_thread_ to perform + // shutdown. + void DoShutdown(); + void DoRegisterSelectedPrinters( + const cloud_print::PrinterList& printer_list); + void DoHandlePrinterNotification(const std::string& printer_id); + + // URLFetcher::Delegate implementation. + virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); +// cloud_print::PrinterChangeNotifier::Delegate implementation + virtual void OnPrinterAdded(); + virtual void OnPrinterDeleted() { + } + virtual void OnPrinterChanged() { + } + virtual void OnJobChanged() { + } + // PrinterJobHandler::Delegate implementation + void OnPrinterJobHandlerShutdown(PrinterJobHandler* job_handler, + const std::string& printer_id); + + protected: + // FrontendNotification defines parameters for NotifyFrontend. Each enum + // value corresponds to the one CloudPrintProcyService method that + // NotifyFrontend should invoke. + enum FrontendNotification { + PRINTER_LIST_AVAILABLE, // OnPrinterListAvailable + }; + // Prototype for a response handler. + typedef void (CloudPrintProxyBackend::Core::*ResponseHandler)( + const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, int response_code, + const ResponseCookies& cookies, const std::string& data); + // Begin response handlers + void HandlePrinterListResponse(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + void HandleRegisterPrinterResponse(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + // End response handlers + + // NotifyFrontend is how the Core communicates with the frontend across + // threads. + void NotifyFrontend(FrontendNotification notification); + // Starts a new printer registration process. + void StartRegistration(); + // Ends the printer registration process. + void EndRegistration(); + // Registers printer capabilities and defaults for the next printer in the + // list with the cloud print server. + void RegisterNextPrinter(); + // Retrieves the list of registered printers for this user/proxy combination + // from the cloud print server. + void GetRegisteredPrinters(); + void HandleServerError(Task* task_to_retry); + // Removes the given printer from the list. Returns false if the printer + // did not exist in the list. + bool RemovePrinterFromList(const std::string& printer_name); + // Initializes a job handler object for the specified printer. The job + // handler is responsible for checking for pending print jobs for this + // printer and print them. + void InitJobHandlerForPrinter(DictionaryValue* printer_data); + + // Our parent CloudPrintProxyBackend + CloudPrintProxyBackend* backend_; + // The list of printers to be registered with the cloud print server. + // To begin with,this list is initialized with the list of local and network + // printers available. Then we query the server for the list of printers + // already registered. We trim this list to remove the printers already + // registered. We then pass a copy of this list to the frontend to give the + // user a chance to further trim the list. When the frontend gives us the + // final list we make a copy into this so that we can start registering. + cloud_print::PrinterList printer_list_; + // The URLFetcher instance for the current request + scoped_ptr<URLFetcher> request_; + // The index of the nex printer to be uploaded. + size_t next_upload_index_; + // The unique id for this proxy + std::string proxy_id_; + // The GAIA auth token + std::string auth_token_; + // The number of consecutive times that connecting to the server failed. + int server_error_count_; + // Cached info about the last printer that we tried to upload. We cache this + // so we won't have to requery the printer if the upload fails and we need + // to retry. + std::string last_uploaded_printer_name_; + cloud_print::PrinterCapsAndDefaults last_uploaded_printer_info_; + // A map of printer id to job handler. + typedef std::map<std::string, scoped_refptr<PrinterJobHandler> > + JobHandlerMap; + JobHandlerMap job_handler_map_; + ResponseHandler next_response_handler_; + cloud_print::PrinterChangeNotifier printer_change_notifier_; + bool new_printers_available_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +CloudPrintProxyBackend::CloudPrintProxyBackend( + CloudPrintProxyFrontend* frontend) + : core_thread_("Chrome_CloudPrintProxyCoreThread"), + frontend_loop_(MessageLoop::current()), + frontend_(frontend) { + DCHECK(frontend_); + core_ = new Core(this); +} + +CloudPrintProxyBackend::~CloudPrintProxyBackend() { + DCHECK(!core_); +} + +bool CloudPrintProxyBackend::Initialize(const std::string& auth_token, + const std::string& proxy_id) { + if (!core_thread_.Start()) + return false; + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod( + core_.get(), &CloudPrintProxyBackend::Core::DoInitialize, auth_token, + proxy_id)); + return true; +} + +void CloudPrintProxyBackend::Shutdown() { + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(core_.get(), + &CloudPrintProxyBackend::Core::DoShutdown)); + core_thread_.Stop(); + core_ = NULL; // Releases reference to core_. +} + +void CloudPrintProxyBackend::RegisterPrinters( + const cloud_print::PrinterList& printer_list) { + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod( + core_.get(), + &CloudPrintProxyBackend::Core::DoRegisterSelectedPrinters, + printer_list)); +} + +void CloudPrintProxyBackend::HandlePrinterNotification( + const std::string& printer_id) { + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod( + core_.get(), + &CloudPrintProxyBackend::Core::DoHandlePrinterNotification, + printer_id)); +} + +CloudPrintProxyBackend::Core::Core(CloudPrintProxyBackend* backend) + : backend_(backend), next_upload_index_(0), server_error_count_(0), + next_response_handler_(NULL), new_printers_available_(false) { +} + +void CloudPrintProxyBackend::Core::DoInitialize(const std::string& auth_token, + const std::string& proxy_id) { + DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop()); + printer_change_notifier_.StartWatching(std::string(), this); + proxy_id_ = proxy_id; + auth_token_ = auth_token; + StartRegistration(); +} + +void CloudPrintProxyBackend::Core::StartRegistration() { + printer_list_.clear(); + cloud_print::EnumeratePrinters(&printer_list_); + server_error_count_ = 0; + // Now we need to ask the server about printers that were registered on the + // server so that we can trim this list. + GetRegisteredPrinters(); +} + +void CloudPrintProxyBackend::Core::EndRegistration() { + request_.reset(); + if (new_printers_available_) { + new_printers_available_ = false; + StartRegistration(); + } +} + +void CloudPrintProxyBackend::Core::DoShutdown() { + // Need to kill all running jobs. + while (!job_handler_map_.empty()) { + JobHandlerMap::iterator index = job_handler_map_.begin(); + // Shutdown will call our OnPrinterJobHandlerShutdown method which will + // remove this from the map. + index->second->Shutdown(); + } +} + +void CloudPrintProxyBackend::Core::DoRegisterSelectedPrinters( + const cloud_print::PrinterList& printer_list) { + server_error_count_ = 0; + printer_list_.assign(printer_list.begin(), printer_list.end()); + DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop()); + next_upload_index_ = 0; + RegisterNextPrinter(); +} + +void CloudPrintProxyBackend::Core::DoHandlePrinterNotification( + const std::string& printer_id) { + JobHandlerMap::iterator index = job_handler_map_.find(printer_id); + if (index != job_handler_map_.end()) + index->second->NotifyJobAvailable(); +} + +void CloudPrintProxyBackend::Core::GetRegisteredPrinters() { + request_.reset( + new URLFetcher(CloudPrintHelpers::GetUrlForPrinterList(proxy_id_), + URLFetcher::GET, this)); + CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); + next_response_handler_ = + &CloudPrintProxyBackend::Core::HandlePrinterListResponse; + request_->Start(); +} + +void CloudPrintProxyBackend::Core::RegisterNextPrinter() { + // For the next printer to be uploaded, create a multi-part post request to + // upload the printer capabilities and the printer defaults. + if (next_upload_index_ < printer_list_.size()) { + const cloud_print::PrinterBasicInfo& info = + printer_list_.at(next_upload_index_); + bool have_printer_info = true; + // If we are retrying a previous upload, we don't need to fetch the caps + // and defaults again. + if (info.printer_name != last_uploaded_printer_name_) { + have_printer_info = cloud_print::GetPrinterCapsAndDefaults( + info.printer_name.c_str(), &last_uploaded_printer_info_); + } + if (have_printer_info) { + last_uploaded_printer_name_ = info.printer_name; + std::string mime_boundary; + CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); + std::string post_data; + CloudPrintHelpers::AddMultipartValueForUpload(kProxyIdValue, proxy_id_, + mime_boundary, + std::string(), &post_data); + CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue, + info.printer_name, + mime_boundary, + std::string(), &post_data); + CloudPrintHelpers::AddMultipartValueForUpload(kPrinterDescValue, + info.printer_description, + mime_boundary, + std::string() , &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterStatusValue, StringPrintf("%d", info.printer_status), + mime_boundary, std::string(), &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterCapsValue, last_uploaded_printer_info_.printer_capabilities, + mime_boundary, last_uploaded_printer_info_.caps_mime_type, + &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterDefaultsValue, last_uploaded_printer_info_.printer_defaults, + mime_boundary, last_uploaded_printer_info_.defaults_mime_type, + &post_data); + // Send a hash of the printer capabilities to the server. We will use this + // later to check if the capabilities have changed + CloudPrintHelpers::AddMultipartValueForUpload( + WideToUTF8(kPrinterCapsHashValue).c_str(), + MD5String(last_uploaded_printer_info_.printer_capabilities), + mime_boundary, std::string(), &post_data); + // Terminate the request body + post_data.append("--" + mime_boundary + "--\r\n"); + std::string mime_type("multipart/form-data; boundary="); + mime_type += mime_boundary; + request_.reset( + new URLFetcher(CloudPrintHelpers::GetUrlForPrinterRegistration(), + URLFetcher::POST, this)); + CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); + request_->set_upload_data(mime_type, post_data); + next_response_handler_ = + &CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse; + request_->Start(); + } else { + NOTREACHED(); + } + } else { + EndRegistration(); + } +} + +// URLFetcher::Delegate implementation. +void CloudPrintProxyBackend::Core::OnURLFetchComplete( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + DCHECK(source == request_.get()); + // We need a next response handler + DCHECK(next_response_handler_); + (this->*next_response_handler_)(source, url, status, response_code, + cookies, data); +} + +void CloudPrintProxyBackend::Core::NotifyFrontend( + FrontendNotification notification) { + switch (notification) { + case PRINTER_LIST_AVAILABLE: + backend_->frontend_->OnPrinterListAvailable(printer_list_); + break; + default: + NOTREACHED(); + break; + } +} + +void CloudPrintProxyBackend::Core::HandlePrinterListResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + if (status.is_success()) { + server_error_count_ = 0; + if (response_code == 200) { + // Parse the response JSON for the list of printers already registered. + bool succeeded = false; + DictionaryValue* response_dict_temp = NULL; + CloudPrintHelpers::ParseResponseJSON(data, &succeeded, + &response_dict_temp); + scoped_ptr<DictionaryValue> response_dict; + response_dict.reset(response_dict_temp); + if (succeeded) { + DCHECK(response_dict.get()); + ListValue* printer_list = NULL; + response_dict->GetList(kPrinterListValue, &printer_list); + // There may be no "printers" value in the JSON + if (printer_list) { + for (size_t index = 0; index < printer_list->GetSize(); index++) { + DictionaryValue* printer_data = NULL; + if (printer_list->GetDictionary(index, &printer_data)) { + std::string printer_name; + printer_data->GetString(kNameValue, &printer_name); + RemovePrinterFromList(printer_name); + InitJobHandlerForPrinter(printer_data); + } else { + NOTREACHED(); + } + } + } + } + } + MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release()); + if (!printer_list_.empty()) { + // Let the frontend know that we have a list of printers available. + backend_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &Core::NotifyFrontend, PRINTER_LIST_AVAILABLE)); + } else { + // No more work to be done here. + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &Core::EndRegistration)); + } + } else { + HandleServerError(NewRunnableMethod(this, &Core::GetRegisteredPrinters)); + } +} + +void CloudPrintProxyBackend::Core::InitJobHandlerForPrinter( + DictionaryValue* printer_data) { + DCHECK(printer_data); + std::string printer_id; + printer_data->GetString(kIdValue, &printer_id); + DCHECK(!printer_id.empty()); + JobHandlerMap::iterator index = job_handler_map_.find(printer_id); + // We might already have a job handler for this printer + if (index == job_handler_map_.end()) { + cloud_print::PrinterBasicInfo printer_info; + printer_data->GetString(kNameValue, &printer_info.printer_name); + DCHECK(!printer_info.printer_name.empty()); + printer_data->GetString(UTF8ToWide(kPrinterDescValue), + &printer_info.printer_description); + printer_data->GetInteger(UTF8ToWide(kPrinterStatusValue), + &printer_info.printer_status); + std::string caps_hash; + printer_data->GetString(kPrinterCapsHashValue, &caps_hash); + scoped_refptr<PrinterJobHandler> job_handler; + job_handler = new PrinterJobHandler(printer_info, printer_id, caps_hash, + auth_token_, this); + job_handler_map_[printer_id] = job_handler; + job_handler->Initialize(); + } +} + +void CloudPrintProxyBackend::Core::HandleRegisterPrinterResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + Task* next_task = + NewRunnableMethod(this, + &CloudPrintProxyBackend::Core::RegisterNextPrinter); + if (status.is_success() && (response_code == 200)) { + bool succeeded = false; + DictionaryValue* response_dict = NULL; + CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); + if (succeeded) { + DCHECK(response_dict); + ListValue* printer_list = NULL; + response_dict->GetList(kPrinterListValue, &printer_list); + // There should be a "printers" value in the JSON + DCHECK(printer_list); + if (printer_list) { + DictionaryValue* printer_data = NULL; + if (printer_list->GetDictionary(0, &printer_data)) { + InitJobHandlerForPrinter(printer_data); + } + } + } + server_error_count_ = 0; + next_upload_index_++; + MessageLoop::current()->PostTask(FROM_HERE, next_task); + } else { + HandleServerError(next_task); + } +} + +void CloudPrintProxyBackend::Core::HandleServerError(Task* task_to_retry) { + CloudPrintHelpers::HandleServerError( + &server_error_count_, -1, kMaxRetryInterval, kBaseRetryInterval, + task_to_retry, NULL); +} + +bool CloudPrintProxyBackend::Core::RemovePrinterFromList( + const std::string& printer_name) { + bool ret = false; + for (cloud_print::PrinterList::iterator index = printer_list_.begin(); + index != printer_list_.end(); index++) { + if (0 == base::strcasecmp(index->printer_name.c_str(), + printer_name.c_str())) { + index = printer_list_.erase(index); + ret = true; + break; + } + } + return ret; +} + +// cloud_print::PrinterChangeNotifier::Delegate implementation +void CloudPrintProxyBackend::Core::OnPrinterAdded() { + if (request_.get()) { + new_printers_available_ = true; + } else { + StartRegistration(); + } +} + +// PrinterJobHandler::Delegate implementation +void CloudPrintProxyBackend::Core::OnPrinterJobHandlerShutdown( + PrinterJobHandler* job_handler, const std::string& printer_id) { + job_handler_map_.erase(printer_id); +} + diff --git a/chrome/browser/printing/cloud_print/cloud_print_proxy_backend.h b/chrome/browser/printing/cloud_print/cloud_print_proxy_backend.h new file mode 100644 index 0000000..675ae12 --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_proxy_backend.h @@ -0,0 +1,70 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_PROXY_BACKEND_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_PROXY_BACKEND_H_ + +#include <map> +#include <string> + +#include "base/thread.h" +#include "chrome/browser/net/url_fetcher.h" +#include "chrome/browser/printing/cloud_print/printer_info.h" + +class CloudPrintProxyService; +class DictionaryValue; + +// CloudPrintProxyFrontend is the interface used by CloudPrintProxyBackend to +// communicate with the entity that created it and, presumably, is interested in +// cloud print proxy related activity. +// NOTE: All methods will be invoked by a CloudPrintProxyBackend on the same +// thread used to create that CloudPrintProxyBackend. +class CloudPrintProxyFrontend { + public: + CloudPrintProxyFrontend() {} + + // There is a list of printers available that can be registered. + virtual void OnPrinterListAvailable( + const cloud_print::PrinterList& printer_list) = 0; + + protected: + // Don't delete through SyncFrontend interface. + virtual ~CloudPrintProxyFrontend() { + } + private: + DISALLOW_COPY_AND_ASSIGN(CloudPrintProxyFrontend); +}; + +class CloudPrintProxyBackend { + public: + explicit CloudPrintProxyBackend(CloudPrintProxyFrontend* frontend); + ~CloudPrintProxyBackend(); + + bool Initialize(const std::string& auth_token, const std::string& proxy_id); + void Shutdown(); + void RegisterPrinters(const cloud_print::PrinterList& printer_list); + void HandlePrinterNotification(const std::string& printer_id); + + private: + // The real guts of SyncBackendHost, to keep the public client API clean. + class Core; + // A thread we dedicate for use to perform initialization and + // authentication. + base::Thread core_thread_; + // Our core, which communicates with AuthWatcher for GAIA authentication and + // which contains printer registration code. + scoped_refptr<Core> core_; + // A reference to the MessageLoop used to construct |this|, so we know how + // to safely talk back to the SyncFrontend. + MessageLoop* const frontend_loop_; + // The frontend which is responsible for displaying UI and updating Prefs + CloudPrintProxyFrontend* frontend_; + + friend class base::RefCountedThreadSafe<CloudPrintProxyBackend::Core>; + + DISALLOW_COPY_AND_ASSIGN(CloudPrintProxyBackend); +}; + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_PROXY_BACKEND_H_ + diff --git a/chrome/browser/printing/cloud_print/cloud_print_proxy_service.cc b/chrome/browser/printing/cloud_print/cloud_print_proxy_service.cc new file mode 100644 index 0000000..75ec15c --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_proxy_service.cc @@ -0,0 +1,103 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" + +#include <stack> +#include <vector> + +#include "base/path_service.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" +#include "chrome/browser/pref_service.h" + +CloudPrintProxyService::CloudPrintProxyService(Profile* profile) { +} + +CloudPrintProxyService::~CloudPrintProxyService() { + Shutdown(); +} + +void CloudPrintProxyService::Initialize() { + PrefService* prefs = g_browser_process->local_state(); + DCHECK(prefs); + prefs->RegisterStringPref(prefs::kCloudPrintProxyId, L""); + prefs->RegisterStringPref(prefs::kCloudPrintProxyName, L""); + prefs->RegisterStringPref(prefs::kCloudPrintAuthToken, L""); +} + + +void CloudPrintProxyService::EnableForUser(const std::string& auth_token) { + if (backend_.get()) + return; + + PrefService* prefs = g_browser_process->local_state(); + DCHECK(prefs); + std::string proxy_id = + WideToUTF8(prefs->GetString(prefs::kCloudPrintProxyId)); + if (proxy_id.empty()) { + // TODO(sanjeevr): Determine whether the proxy id should be server generated + proxy_id = cloud_print::GenerateProxyId(); + prefs->SetString(prefs::kCloudPrintProxyId, UTF8ToWide(proxy_id)); + } + std::string token_to_use = auth_token; + if (token_to_use.empty()) { + token_to_use = WideToUTF8(prefs->GetString(prefs::kCloudPrintAuthToken)); + } else { + prefs->SetString(prefs::kCloudPrintAuthToken, UTF8ToWide(token_to_use)); + } + + backend_.reset(new CloudPrintProxyBackend(this)); + backend_->Initialize(token_to_use, proxy_id); +} + +void CloudPrintProxyService::DisableForUser() { + PrefService* prefs = g_browser_process->local_state(); + DCHECK(prefs); + prefs->ClearPref(prefs::kCloudPrintAuthToken); + Shutdown(); +} + +void CloudPrintProxyService::HandlePrinterNotification( + const std::string& printer_id) { + if (backend_.get()) + backend_->HandlePrinterNotification(printer_id); +} + +void CloudPrintProxyService::Shutdown() { + if (backend_.get()) + backend_->Shutdown(); + backend_.reset(); +} + +// Notification methods from the backend. Called on UI thread. +void CloudPrintProxyService::OnPrinterListAvailable( + const cloud_print::PrinterList& printer_list) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + // Here we will trim the list to eliminate printers already registered. + // If there are any more printers left in the list after trimming, we will + // show the print selection UI. Any printers left in the list after the user + // selection process will then be registered. + backend_->RegisterPrinters(printer_list); +} + +// Called when authentication is done. Called on UI thread. +// Note that sid can be empty. This is a temp function to steal the sid +// from the Bookmarks Sync code. When the common GAIA signin code is done, +// The CloudPrintProxyService will simply get a notification when authentication +// done with an lsid and a sid. +void CloudPrintProxyService::OnAuthenticated(const std::string& sid) { + FilePath user_data_dir; + PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); + ProfileManager* profile_manager = g_browser_process->profile_manager(); + Profile* profile = profile_manager->GetDefaultProfile(user_data_dir); + profile->GetCloudPrintProxyService()->EnableForUser(sid); +} + diff --git a/chrome/browser/printing/cloud_print/cloud_print_proxy_service.h b/chrome/browser/printing/cloud_print/cloud_print_proxy_service.h new file mode 100644 index 0000000..92985e2 --- /dev/null +++ b/chrome/browser/printing/cloud_print/cloud_print_proxy_service.h @@ -0,0 +1,59 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_PROXY_SERVICE_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_PROXY_SERVICE_H_ + +#include <string> +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/observer_list.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/profile.h" + +#include "chrome/browser/printing/cloud_print/cloud_print_proxy_backend.h" + +class Profile; + +// Layer between the browser user interface and the cloud print proxy backend. +class CloudPrintProxyService : public CloudPrintProxyFrontend { + public: + explicit CloudPrintProxyService(Profile* profile); + virtual ~CloudPrintProxyService(); + + // Initializes the object. This should be called every time an object of this + // class is constructed. + void Initialize(); + + // Enables/disables cloud printing for the user + virtual void EnableForUser(const std::string& auth_token); + virtual void DisableForUser(); + // Notification received from the server for a particular printer. + // We need to inform the backend to look for jobs for this printer. + void HandlePrinterNotification(const std::string& printer_id); + + // Notification methods from the backend. Called on UI thread. + void OnPrinterListAvailable(const cloud_print::PrinterList& printer_list); + + // Called when authentication is done. Called on UI thread. + // Note that sid can be empty. This is placeholder code until we can get + // common GAIA signin code in place. + // TODO(sanjeevr): Remove this. + static void OnAuthenticated(const std::string& auth_token); + + protected: + void Shutdown(); + + // Our asynchronous backend to communicate with sync components living on + // other threads. + scoped_ptr<CloudPrintProxyBackend> backend_; + + DISALLOW_COPY_AND_ASSIGN(CloudPrintProxyService); +}; + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_CLOUD_PRINT_PROXY_SERVICE_H_ + diff --git a/chrome/browser/printing/cloud_print/job_status_updater.cc b/chrome/browser/printing/cloud_print/job_status_updater.cc new file mode 100644 index 0000000..a17c451 --- /dev/null +++ b/chrome/browser/printing/cloud_print/job_status_updater.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/job_status_updater.h" + +#include "base/json/json_reader.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/printing/cloud_print/cloud_print_consts.h" +#include "chrome/browser/printing/cloud_print/cloud_print_helpers.h" +#include "googleurl/src/gurl.h" + +JobStatusUpdater::JobStatusUpdater(const std::string& printer_name, + const std::string& job_id, + cloud_print::PlatformJobId& local_job_id, + const std::string& auth_token, + Delegate* delegate) + : printer_name_(printer_name), job_id_(job_id), + local_job_id_(local_job_id), auth_token_(auth_token), + delegate_(delegate), stopped_(false) { + DCHECK(delegate_); +} + +// Start checking the status of the local print job. +void JobStatusUpdater::UpdateStatus() { + // It does not matter if we had already sent out an update and are waiting for + // a response. This is a new update and we will simply cancel the old request + // and send a new one. + if (!stopped_) { + bool need_update = false; + // If the job has already been completed, we just need to update the server + // with that status. The *only* reason we would come back here in that case + // is if our last server update attempt failed. + if (last_job_details_.status == cloud_print::PRINT_JOB_STATUS_COMPLETED) { + need_update = true; + } else { + cloud_print::PrintJobDetails details; + if (cloud_print::GetJobDetails(printer_name_, local_job_id_, &details)) { + if (details != last_job_details_) { + last_job_details_ = details; + need_update = true; + } + } else { + // If GetJobDetails failed, the most likely case is that the job no + // longer exists in the OS queue. We are going to assume it is done in + // this case. + last_job_details_.Clear(); + last_job_details_.status = cloud_print::PRINT_JOB_STATUS_COMPLETED; + need_update = true; + } + } + if (need_update) { + GURL update_url = CloudPrintHelpers::GetUrlForJobStatusUpdate( + job_id_, last_job_details_); + request_.reset(new URLFetcher(update_url, URLFetcher::GET, this)); + CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); + request_->Start(); + } + } +} + +void JobStatusUpdater::Stop() { + request_.reset(); + DCHECK(delegate_); + stopped_ = true; + delegate_->OnJobCompleted(this); +} + +// URLFetcher::Delegate implementation. +void JobStatusUpdater::OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + int64 next_update_interval = kJobStatusUpdateInterval; + if (!status.is_success() || (response_code != 200)) { + next_update_interval *= 10; + MessageLoop::current()->PostDelayedTask( + FROM_HERE, NewRunnableMethod(this, &JobStatusUpdater::UpdateStatus), + next_update_interval); + } else if (last_job_details_.status == + cloud_print::PRINT_JOB_STATUS_COMPLETED) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &JobStatusUpdater::Stop)); + } +} + diff --git a/chrome/browser/printing/cloud_print/job_status_updater.h b/chrome/browser/printing/cloud_print/job_status_updater.h new file mode 100644 index 0000000..d480621 --- /dev/null +++ b/chrome/browser/printing/cloud_print/job_status_updater.h @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_JOB_STATUS_UPDATER_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_JOB_STATUS_UPDATER_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "chrome/browser/printing/cloud_print/printer_info.h" +#include "chrome/browser/net/url_fetcher.h" +#include "net/url_request/url_request_status.h" + +// Periodically monitors the status of a local print job and updates the +// cloud print server accordingly. When the job has been completed this +// object releases the reference to itself which should cause it to +// self-destruct. +class JobStatusUpdater : public base::RefCountedThreadSafe<JobStatusUpdater>, + public URLFetcher::Delegate { + public: + class Delegate { + public: + virtual bool OnJobCompleted(JobStatusUpdater* updater) = 0; + }; + + JobStatusUpdater(const std::string& printer_name, + const std::string& job_id, + cloud_print::PlatformJobId& local_job_id, + const std::string& auth_token, + Delegate* delegate); + // Checks the status of the local print job and sends an update. + void UpdateStatus(); + void Stop(); + // URLFetcher::Delegate implementation. + virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + private: + std::string printer_name_; + std::string job_id_; + cloud_print::PlatformJobId local_job_id_; + cloud_print::PrintJobDetails last_job_details_; + scoped_ptr<URLFetcher> request_; + std::string auth_token_; + Delegate* delegate_; + // A flag that is set to true in Stop() and will ensure the next scheduled + // task will do nothing. + bool stopped_; + DISALLOW_COPY_AND_ASSIGN(JobStatusUpdater); +}; + +// This typedef is to workaround the issue with certain versions of +// Visual Studio where it gets confused between multiple Delegate +// classes and gives a C2500 error. (I saw this error on the try bots - +// the workaround was not needed for my machine). +typedef JobStatusUpdater::Delegate JobStatusUpdaterDelegate; + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_JOB_STATUS_UPDATER_H_ + diff --git a/chrome/browser/printing/cloud_print/printer_info.h b/chrome/browser/printing/cloud_print/printer_info.h new file mode 100644 index 0000000..1bb1746 --- /dev/null +++ b/chrome/browser/printing/cloud_print/printer_info.h @@ -0,0 +1,125 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRINTER_INFO_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRINTER_INFO_H_ + +#include <string> +#include <vector> + +#include "base/file_path.h" + +// This is the interface for platform-specific code for cloud print +namespace cloud_print { + +typedef int PlatformJobId; + +struct PrinterBasicInfo { + std::string printer_name; + std::string printer_description; + int printer_status; + PrinterBasicInfo() : printer_status(0) { + } +}; + +typedef std::vector<PrinterBasicInfo> PrinterList; + +struct PrinterCapsAndDefaults { + std::string printer_capabilities; + std::string caps_mime_type; + std::string printer_defaults; + std::string defaults_mime_type; +}; + +enum PrintJobStatus { + PRINT_JOB_STATUS_INVALID, + PRINT_JOB_STATUS_IN_PROGRESS, + PRINT_JOB_STATUS_ERROR, + PRINT_JOB_STATUS_COMPLETED +}; + +struct PrintJobDetails { + PrintJobStatus status; + int platform_status_flags; + std::string status_message; + int total_pages; + int pages_printed; + PrintJobDetails() : status(PRINT_JOB_STATUS_INVALID), + platform_status_flags(0), total_pages(0), + pages_printed(0) { + } + void Clear() { + status = PRINT_JOB_STATUS_INVALID; + platform_status_flags = 0; + status_message.clear(); + total_pages = 0; + pages_printed = 0; + } + bool operator ==(const PrintJobDetails& other) const { + return (status == other.status) && + (platform_status_flags == other.platform_status_flags) && + (status_message == other.status_message) && + (total_pages == other.total_pages) && + (pages_printed == other.pages_printed); + } + bool operator !=(const PrintJobDetails& other) const { + return !(*this == other); + } +}; + +// Enumerates the list of installed local and network printers. +void EnumeratePrinters(PrinterList* printer_list); +// Gets the capabilities and defaults for a specific printer. +bool GetPrinterCapsAndDefaults(const std::string& printer_name, + PrinterCapsAndDefaults* printer_info); +bool ValidatePrintTicket(const std::string& printer_name, + const std::string& print_ticket_data); +std::string GenerateProxyId(); +bool SpoolPrintJob(const std::string& print_ticket, + const FilePath& print_data_file_path, + const std::string& print_data_mime_type, + const std::string& printer_name, + const std::string& job_title, + PlatformJobId* job_id_ret); + +bool GetJobDetails(const std::string& printer_name, + PlatformJobId job_id, + PrintJobDetails *job_details); +bool IsValidPrinter(const std::string& printer_name); + +// A class that watches changes to a printer or a print server. +// The set of notifications are very coarse-grained (even though the Windows +// API allows for listening to fine-grained details about a printer, this class +// does not support that level of fine-grained control. +class PrinterChangeNotifier { + public: + class Delegate { + public: + virtual void OnPrinterAdded() = 0; + virtual void OnPrinterDeleted() = 0; + virtual void OnPrinterChanged() = 0; + virtual void OnJobChanged() = 0; + }; + PrinterChangeNotifier(); + ~PrinterChangeNotifier(); + bool StartWatching(const std::string& printer_name, Delegate* delegate); + bool StopWatching(); + bool GetCurrentPrinterInfo(PrinterBasicInfo* printer_info); + private: + // Internal state maintained by the PrinterChangeNotifier class. + class NotificationState; + NotificationState* state_; + DISALLOW_COPY_AND_ASSIGN(PrinterChangeNotifier); +}; + +// This typedef is to workaround the issue with certain versions of +// Visual Studio where it gets confused between multiple Delegate +// classes and gives a C2500 error. (I saw this error on the try bots - +// the workaround was not needed for my machine). +typedef PrinterChangeNotifier::Delegate PrinterChangeNotifierDelegate; + +} // namespace cloud_print + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRINTER_INFO_H_ + diff --git a/chrome/browser/printing/cloud_print/printer_info_linux.cc b/chrome/browser/printing/cloud_print/printer_info_linux.cc new file mode 100644 index 0000000..0715bb8 --- /dev/null +++ b/chrome/browser/printing/cloud_print/printer_info_linux.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/printer_info.h" + +#include "base/logging.h" + +// TODO(sanjeevr): Implement the Linux interfaces. +namespace cloud_print { + +void EnumeratePrinters(PrinterList* printer_list) { + DCHECK(printer_list); + NOTIMPLEMENTED(); +} + +bool GetPrinterCapsAndDefaults(const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + NOTIMPLEMENTED(); + return false; +} + +bool ValidatePrintTicket(const std::string& printer_name, + const std::string& print_ticket_data) { + NOTIMPLEMENTED(); + return false; +} + +std::string GenerateProxyId() { + NOTIMPLEMENTED(); + return std::string(); +} + +bool SpoolPrintJob(const std::string& print_ticket, + const FilePath& print_data_file_path, + const std::string& print_data_mime_type, + const std::string& printer_name, + const std::string& job_title, + PlatformJobId* job_id_ret) { + NOTIMPLEMENTED(); + return false; +} + +bool GetJobDetails(const std::string& printer_name, + PlatformJobId job_id, + PrintJobDetails *job_details) { + NOTIMPLEMENTED(); + return false; +} + +bool IsValidPrinter(const std::string& printer_name) { + NOTIMPLEMENTED(); + return false; +} + +PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) { +} + +PrinterChangeNotifier::~PrinterChangeNotifier() { + StopWatching(); +} + +bool PrinterChangeNotifier::StartWatching(const std::string& printer_name, + Delegate* delegate) { + NOTIMPLEMENTED(); + return false; +} + +bool PrinterChangeNotifier::StopWatching() { + NOTIMPLEMENTED(); + return false; +} + +bool PrinterChangeNotifier::GetCurrentPrinterInfo( + PrinterBasicInfo* printer_info) { + NOTIMPLEMENTED(); + return false; +} +} // namespace cloud_print + diff --git a/chrome/browser/printing/cloud_print/printer_info_mac.cc b/chrome/browser/printing/cloud_print/printer_info_mac.cc new file mode 100644 index 0000000..dd0dbad --- /dev/null +++ b/chrome/browser/printing/cloud_print/printer_info_mac.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/printer_info.h" + +#include "base/logging.h" + +// TODO(sanjeevr): Implement the Mac interfaces. +namespace cloud_print { + +void EnumeratePrinters(PrinterList* printer_list) { + DCHECK(printer_list); + NOTIMPLEMENTED(); +} + +bool GetPrinterCapsAndDefaults(const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + NOTIMPLEMENTED(); + return false; +} + +bool ValidatePrintTicket(const std::string& printer_name, + const std::string& print_ticket_data) { + NOTIMPLEMENTED(); + return false; +} + +std::string GenerateProxyId() { + NOTIMPLEMENTED(); + return std::string(); +} + +bool SpoolPrintJob(const std::string& print_ticket, + const FilePath& print_data_file_path, + const std::string& print_data_mime_type, + const std::string& printer_name, + const std::string& job_title, + PlatformJobId* job_id_ret) { + NOTIMPLEMENTED(); + return false; +} + +bool GetJobDetails(const std::string& printer_name, + PlatformJobId job_id, + PrintJobDetails *job_details) { + NOTIMPLEMENTED(); + return false; +} + +bool IsValidPrinter(const std::string& printer_name) { + NOTIMPLEMENTED(); + return false; +} + +PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) { +} + +PrinterChangeNotifier::~PrinterChangeNotifier() { + StopWatching(); +} + +bool PrinterChangeNotifier::StartWatching(const std::string& printer_name, + Delegate* delegate) { + NOTIMPLEMENTED(); + return false; +} + +bool PrinterChangeNotifier::StopWatching() { + NOTIMPLEMENTED(); + return false; +} + +bool PrinterChangeNotifier::GetCurrentPrinterInfo( + PrinterBasicInfo* printer_info) { + NOTIMPLEMENTED(); + return false; +} +} // namespace cloud_print + diff --git a/chrome/browser/printing/cloud_print/printer_info_win.cc b/chrome/browser/printing/cloud_print/printer_info_win.cc new file mode 100644 index 0000000..b1ec208 --- /dev/null +++ b/chrome/browser/printing/cloud_print/printer_info_win.cc @@ -0,0 +1,509 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/printer_info.h" + +#include <windows.h> +#include <objidl.h> +#include <ocidl.h> +#include <olectl.h> +#include <prntvpt.h> +#include <winspool.h> + +#include "base/lock.h" +#include "base/object_watcher.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_handle_win.h" +#include "base/scoped_ptr.h" +#include "base/utf_string_conversions.h" + +#pragma comment(lib, "prntvpt.lib") +#pragma comment(lib, "rpcrt4.lib") + +namespace { + +class DevMode { + public: + DevMode() : dm_(NULL) {} + ~DevMode() { Free(); } + + void Allocate(int size) { + Free(); + dm_ = reinterpret_cast<DEVMODE*>(new char[size]); + } + + void Free() { + if (dm_) + delete dm_; + dm_ = NULL; + } + + DEVMODE* dm_; + + private: + DISALLOW_COPY_AND_ASSIGN(DevMode); +}; + +bool InitXPSModule() { + HMODULE prntvpt_module = LoadLibrary(L"prntvpt.dll"); + return (NULL != prntvpt_module); +} + +inline HRESULT GetLastErrorHR() { + LONG error = GetLastError(); + return HRESULT_FROM_WIN32(error); +} + +HRESULT StreamFromPrintTicket(const std::string& print_ticket, + IStream** stream) { + DCHECK(stream); + HRESULT hr = CreateStreamOnHGlobal(NULL, TRUE, stream); + if (FAILED(hr)) { + return hr; + } + ULONG bytes_written = 0; + (*stream)->Write(print_ticket.c_str(), print_ticket.length(), &bytes_written); + DCHECK(bytes_written == print_ticket.length()); + LARGE_INTEGER pos = {0}; + ULARGE_INTEGER new_pos = {0}; + (*stream)->Seek(pos, STREAM_SEEK_SET, &new_pos); + return S_OK; +} + +HRESULT StreamOnHGlobalToString(IStream* stream, std::string* out) { + DCHECK(stream); + DCHECK(out); + HGLOBAL hdata = NULL; + HRESULT hr = GetHGlobalFromStream(stream, &hdata); + if (SUCCEEDED(hr)) { + DCHECK(hdata); + ScopedHGlobal<char> locked_data(hdata); + out->assign(locked_data.release(), locked_data.Size()); + } + return hr; +} + +HRESULT PrintTicketToDevMode(const std::string& printer_name, + const std::string& print_ticket, + DevMode* dev_mode) { + DCHECK(dev_mode); + + ScopedComPtr<IStream> pt_stream; + HRESULT hr = StreamFromPrintTicket(print_ticket, pt_stream.Receive()); + if (FAILED(hr)) + return hr; + + HPTPROVIDER provider = NULL; + hr = PTOpenProvider(UTF8ToWide(printer_name).c_str(), 1, &provider); + if (SUCCEEDED(hr)) { + ULONG size = 0; + DEVMODE* dm = NULL; + hr = PTConvertPrintTicketToDevMode(provider, + pt_stream, + kUserDefaultDevmode, + kPTDocumentScope, + &size, + &dm, + NULL); + if (SUCCEEDED(hr)) { + dev_mode->Allocate(size); + memcpy(dev_mode->dm_, dm, size); + PTReleaseMemory(dm); + } + PTCloseProvider(provider); + } + return hr; +} + +HRESULT PrintPdf2DC(HDC dc, const FilePath& pdf_filename) { + HRESULT hr = E_NOTIMPL; + // TODO(sanjeevr): Implement this. + NOTIMPLEMENTED(); + return hr; +} + +} // namespace + +namespace cloud_print { + +void EnumeratePrinters(PrinterList* printer_list) { + DCHECK(printer_list); + DWORD bytes_needed = 0; + DWORD count_returned = 0; + BOOL ret = EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 2, + NULL, 0, &bytes_needed, &count_returned); + if (0 != bytes_needed) { + scoped_ptr<BYTE> printer_info_buffer(new BYTE[bytes_needed]); + ret = EnumPrinters(PRINTER_ENUM_LOCAL|PRINTER_ENUM_CONNECTIONS, NULL, 2, + printer_info_buffer.get(), bytes_needed, &bytes_needed, + &count_returned); + DCHECK(ret); + PRINTER_INFO_2* printer_info = + reinterpret_cast<PRINTER_INFO_2*>(printer_info_buffer.get()); + for (DWORD index = 0; index < count_returned; index++) { + PrinterBasicInfo info; + info.printer_name = WideToUTF8(printer_info[index].pPrinterName); + if (printer_info[index].pComment) + info.printer_description = WideToUTF8(printer_info[index].pComment); + info.printer_status = printer_info[index].Status; + printer_list->push_back(info); + } + } +} + +bool GetPrinterCapsAndDefaults(const std::string& printer_name, + PrinterCapsAndDefaults* printer_info) { + if (!InitXPSModule()) { + // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) + return false; + } + if (!IsValidPrinter(printer_name)) { + return false; + } + DCHECK(printer_info); + HPTPROVIDER provider = NULL; + std::wstring printer_name_wide = UTF8ToWide(printer_name); + HRESULT hr = PTOpenProvider(printer_name_wide.c_str(), 1, &provider); + DCHECK(SUCCEEDED(hr)); + if (provider) { + ScopedComPtr<IStream> print_capabilities_stream; + hr = CreateStreamOnHGlobal(NULL, TRUE, + print_capabilities_stream.Receive()); + DCHECK(SUCCEEDED(hr)); + if (print_capabilities_stream) { + ScopedBstr error; + hr = PTGetPrintCapabilities(provider, NULL, print_capabilities_stream, + error.Receive()); + DCHECK(SUCCEEDED(hr)); + if (FAILED(hr)) { + return false; + } + hr = StreamOnHGlobalToString(print_capabilities_stream.get(), + &printer_info->printer_capabilities); + DCHECK(SUCCEEDED(hr)); + printer_info->caps_mime_type = "text/xml"; + } + // TODO(sanjeevr): Add ScopedPrinterHandle + HANDLE printer_handle = NULL; + OpenPrinter(const_cast<LPTSTR>(printer_name_wide.c_str()), &printer_handle, + NULL); + DCHECK(printer_handle); + if (printer_handle) { + DWORD devmode_size = DocumentProperties( + NULL, printer_handle, const_cast<LPTSTR>(printer_name_wide.c_str()), + NULL, NULL, 0); + DCHECK(0 != devmode_size); + scoped_ptr<BYTE> devmode_out_buffer(new BYTE[devmode_size]); + DEVMODE* devmode_out = + reinterpret_cast<DEVMODE*>(devmode_out_buffer.get()); + DocumentProperties( + NULL, printer_handle, const_cast<LPTSTR>(printer_name_wide.c_str()), + devmode_out, NULL, DM_OUT_BUFFER); + ScopedComPtr<IStream> printer_defaults_stream; + hr = CreateStreamOnHGlobal(NULL, TRUE, + printer_defaults_stream.Receive()); + DCHECK(SUCCEEDED(hr)); + if (printer_defaults_stream) { + hr = PTConvertDevModeToPrintTicket(provider, devmode_size, + devmode_out, kPTJobScope, + printer_defaults_stream); + DCHECK(SUCCEEDED(hr)); + if (SUCCEEDED(hr)) { + hr = StreamOnHGlobalToString(printer_defaults_stream.get(), + &printer_info->printer_defaults); + DCHECK(SUCCEEDED(hr)); + printer_info->defaults_mime_type = "text/xml"; + } + } + ClosePrinter(printer_handle); + } + PTCloseProvider(provider); + } + return true; +} + +bool ValidatePrintTicket(const std::string& printer_name, + const std::string& print_ticket_data) { + if (!InitXPSModule()) { + // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) + return false; + } + bool ret = false; + HPTPROVIDER provider = NULL; + PTOpenProvider(UTF8ToWide(printer_name.c_str()).c_str(), 1, &provider); + if (provider) { + ScopedComPtr<IStream> print_ticket_stream; + CreateStreamOnHGlobal(NULL, TRUE, print_ticket_stream.Receive()); + ULONG bytes_written = 0; + print_ticket_stream->Write(print_ticket_data.c_str(), + print_ticket_data.length(), + &bytes_written); + DCHECK(bytes_written == print_ticket_data.length()); + LARGE_INTEGER pos = {0}; + ULARGE_INTEGER new_pos = {0}; + print_ticket_stream->Seek(pos, STREAM_SEEK_SET, &new_pos); + ScopedBstr error; + ScopedComPtr<IStream> result_ticket_stream; + CreateStreamOnHGlobal(NULL, TRUE, result_ticket_stream.Receive()); + ret = SUCCEEDED(PTMergeAndValidatePrintTicket(provider, + print_ticket_stream.get(), + NULL, + kPTJobScope, + result_ticket_stream.get(), + error.Receive())); + PTCloseProvider(provider); + } + return ret; +} + +std::string GenerateProxyId() { + GUID proxy_id = {0}; + HRESULT hr = UuidCreate(&proxy_id); + DCHECK(SUCCEEDED(hr)); + wchar_t* proxy_id_as_string = NULL; + UuidToString(&proxy_id, reinterpret_cast<RPC_WSTR *>(&proxy_id_as_string)); + DCHECK(proxy_id_as_string); + std::string ret; + WideToUTF8(proxy_id_as_string, wcslen(proxy_id_as_string), &ret); + RpcStringFree(reinterpret_cast<RPC_WSTR *>(&proxy_id_as_string)); + return ret; +} + +bool SpoolPrintJob(const std::string& print_ticket, + const FilePath& print_data_file_path, + const std::string& print_data_mime_type, + const std::string& printer_name, + const std::string& job_title, + PlatformJobId* job_id_ret) { + if (!InitXPSModule()) { + // TODO(sanjeevr): Handle legacy proxy case (with no prntvpt.dll) + return false; + } + DevMode pt_dev_mode; + HRESULT hr = PrintTicketToDevMode(printer_name, print_ticket, &pt_dev_mode); + if (FAILED(hr)) { + NOTREACHED(); + return false; + } + ScopedHDC dc(CreateDC(L"WINSPOOL", UTF8ToWide(printer_name).c_str(), NULL, + pt_dev_mode.dm_)); + if (!dc.Get()) { + NOTREACHED(); + return false; + } + hr = E_FAIL; + DOCINFO di = {0}; + di.cbSize = sizeof(DOCINFO); + std::wstring doc_name = UTF8ToWide(job_title); + di.lpszDocName = doc_name.c_str(); + int job_id = StartDoc(dc.Get(), &di); + if (SP_ERROR != job_id) { + if (print_data_mime_type == "application/pdf") { + hr = PrintPdf2DC(dc.Get(), print_data_file_path); + } else { + NOTREACHED(); + } + EndDoc(dc.Get()); + if (SUCCEEDED(hr) && job_id_ret) { + *job_id_ret = job_id; + } + } + return SUCCEEDED(hr); +} + +bool GetJobDetails(const std::string& printer_name, + PlatformJobId job_id, + PrintJobDetails *job_details) { + DCHECK(job_details); + HANDLE printer_handle = NULL; + std::wstring printer_name_wide = UTF8ToWide(printer_name); + OpenPrinter(const_cast<LPTSTR>(printer_name_wide.c_str()), &printer_handle, + NULL); + DCHECK(printer_handle); + bool ret = false; + if (printer_handle) { + DWORD bytes_needed = 0; + GetJob(printer_handle, job_id, 1, NULL, 0, &bytes_needed); + DWORD last_error = GetLastError(); + if (ERROR_INVALID_PARAMETER != last_error) { + // ERROR_INVALID_PARAMETER normally means that the job id is not valid. + DCHECK(last_error == ERROR_INSUFFICIENT_BUFFER); + scoped_ptr<BYTE> job_info_buffer(new BYTE[bytes_needed]); + if (GetJob(printer_handle, job_id, 1, job_info_buffer.get(), bytes_needed, + &bytes_needed)) { + JOB_INFO_1 *job_info = + reinterpret_cast<JOB_INFO_1 *>(job_info_buffer.get()); + if (job_info->pStatus) { + WideToUTF8(job_info->pStatus, wcslen(job_info->pStatus), + &job_details->status_message); + } + job_details->platform_status_flags = job_info->Status; + if ((job_info->Status & JOB_STATUS_COMPLETE) || + (job_info->Status & JOB_STATUS_PRINTED)) { + job_details->status = PRINT_JOB_STATUS_COMPLETED; + } else if (job_info->Status & JOB_STATUS_ERROR) { + job_details->status = PRINT_JOB_STATUS_ERROR; + } else { + job_details->status = PRINT_JOB_STATUS_IN_PROGRESS; + } + job_details->total_pages = job_info->TotalPages; + job_details->pages_printed = job_info->PagesPrinted; + ret = true; + } + } + ClosePrinter(printer_handle); + } + return ret; +} + +bool IsValidPrinter(const std::string& printer_name) { + std::wstring printer_name_wide = UTF8ToWide(printer_name); + HANDLE printer_handle = NULL; + OpenPrinter(const_cast<LPTSTR>(printer_name_wide.c_str()), &printer_handle, + NULL); + bool ret = false; + if (printer_handle) { + ret = true; + ClosePrinter(printer_handle); + } + return ret; +} + +class PrinterChangeNotifier::NotificationState + : public base::ObjectWatcher::Delegate { + public: + NotificationState() : printer_(NULL), printer_change_(NULL), delegate_(NULL) { + } + ~NotificationState() { + Stop(); + } + bool Start(const std::string& printer_name, + PrinterChangeNotifier::Delegate* delegate) { + delegate_ = delegate; + // An empty printer name means watch the current server, we need to pass + // NULL to OpenPrinter. + LPTSTR printer_name_to_use = NULL; + std::wstring printer_name_wide; + if (!printer_name.empty()) { + printer_name_wide = UTF8ToWide(printer_name); + printer_name_to_use = const_cast<LPTSTR>(printer_name_wide.c_str()); + } + bool ret = false; + OpenPrinter(printer_name_to_use, &printer_, NULL); + if (printer_) { + printer_change_ = FindFirstPrinterChangeNotification( + printer_, PRINTER_CHANGE_PRINTER|PRINTER_CHANGE_JOB, 0, NULL); + if (printer_change_) { + ret = watcher_.StartWatching(printer_change_, this); + } + } + if (!ret) { + Stop(); + } + return ret; + } + bool Stop() { + watcher_.StopWatching(); + if (printer_) { + ClosePrinter(printer_); + printer_ = NULL; + } + if (printer_change_) { + FindClosePrinterChangeNotification(printer_change_); + printer_change_ = NULL; + } + return true; + } + + void OnObjectSignaled(HANDLE object) { + DWORD change = 0; + FindNextPrinterChangeNotification(object, &change, NULL, NULL); + + if (change != ((PRINTER_CHANGE_PRINTER|PRINTER_CHANGE_JOB) & + (~PRINTER_CHANGE_FAILED_CONNECTION_PRINTER))) { + // For printer connections, we get spurious change notifications with + // all flags set except PRINTER_CHANGE_FAILED_CONNECTION_PRINTER. + // Ignore these. + if (change & PRINTER_CHANGE_ADD_PRINTER) { + delegate_->OnPrinterAdded(); + } else if (change & PRINTER_CHANGE_DELETE_PRINTER) { + delegate_->OnPrinterDeleted(); + } else if (change & PRINTER_CHANGE_SET_PRINTER) { + delegate_->OnPrinterChanged(); + } + if (change & PRINTER_CHANGE_JOB) { + delegate_->OnJobChanged(); + } + } + watcher_.StartWatching(printer_change_, this); + } + HANDLE printer_handle() const { + return printer_; + } + private: + base::ObjectWatcher watcher_; + HANDLE printer_; // The printer being watched + HANDLE printer_change_; // Returned by FindFirstPrinterChangeNotifier + PrinterChangeNotifier::Delegate* delegate_; // Delegate to notify + bool did_signal_; // DoneWaiting was called +}; + +PrinterChangeNotifier::PrinterChangeNotifier() : state_(NULL) { +} + +PrinterChangeNotifier::~PrinterChangeNotifier() { + StopWatching(); +} + +bool PrinterChangeNotifier::StartWatching(const std::string& printer_name, + Delegate* delegate) { + if (state_) { + NOTREACHED(); + return false; + } + state_ = new NotificationState; + if (!state_->Start(printer_name, delegate)) { + StopWatching(); + return false; + } + return true; +} + +bool PrinterChangeNotifier::StopWatching() { + if (!state_) { + return false; + } + state_->Stop(); + delete state_; + state_ = NULL; + return true; +} + +bool PrinterChangeNotifier::GetCurrentPrinterInfo( + PrinterBasicInfo* printer_info) { + if (!state_) { + return false; + } + DCHECK(printer_info); + DWORD bytes_needed = 0; + bool ret = false; + GetPrinter(state_->printer_handle(), 2, NULL, 0, &bytes_needed); + if (0 != bytes_needed) { + scoped_ptr<BYTE> printer_info_buffer(new BYTE[bytes_needed]); + if (GetPrinter(state_->printer_handle(), 2, printer_info_buffer.get(), + bytes_needed, &bytes_needed)) { + PRINTER_INFO_2* printer_info_win = + reinterpret_cast<PRINTER_INFO_2*>(printer_info_buffer.get()); + printer_info->printer_name = WideToUTF8(printer_info_win->pPrinterName); + printer_info->printer_description = + WideToUTF8(printer_info_win->pComment); + printer_info->printer_status = printer_info_win->Status; + ret = true; + } + } + return ret; +} +} // namespace cloud_print + diff --git a/chrome/browser/printing/cloud_print/printer_job_handler.cc b/chrome/browser/printing/cloud_print/printer_job_handler.cc new file mode 100644 index 0000000..a27fdf0 --- /dev/null +++ b/chrome/browser/printing/cloud_print/printer_job_handler.cc @@ -0,0 +1,560 @@ +// Copyright (c) 2010 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 "chrome/browser/printing/cloud_print/printer_job_handler.h" + +#include "base/file_util.h" +#include "base/json/json_reader.h" +#include "base/md5.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/printing/cloud_print/cloud_print_consts.h" +#include "chrome/browser/printing/cloud_print/cloud_print_helpers.h" +#include "chrome/browser/printing/cloud_print/job_status_updater.h" +#include "googleurl/src/gurl.h" +#include "net/http/http_response_headers.h" + +PrinterJobHandler::PrinterJobHandler( + const cloud_print::PrinterBasicInfo& printer_info, + const std::string& printer_id, + const std::string& caps_hash, + const std::string& auth_token, + Delegate* delegate) + : printer_info_(printer_info), + printer_id_(printer_id), + auth_token_(auth_token), + last_caps_hash_(caps_hash), + delegate_(delegate), + local_job_id_(-1), + next_response_handler_(NULL), + server_error_count_(0), + print_thread_("Chrome_CloudPrintJobPrintThread"), + shutting_down_(false), + server_job_available_(false), + printer_update_pending_(true), + printer_delete_pending_(false), + task_in_progress_(false) { +} + +bool PrinterJobHandler::Initialize() { + if (cloud_print::IsValidPrinter(printer_info_.printer_name)) { + printer_change_notifier_.StartWatching(printer_info_.printer_name, this); + NotifyJobAvailable(); + } else { + // This printer does not exist any more. Delete it from the server. + OnPrinterDeleted(); + } + return true; +} + +PrinterJobHandler::~PrinterJobHandler() { + printer_change_notifier_.StopWatching(); +} + +void PrinterJobHandler::Reset() { + print_data_url_.clear(); + job_details_.Clear(); + request_.reset(); + print_thread_.Stop(); +} + +void PrinterJobHandler::Start() { + if (task_in_progress_) { + // Multiple Starts can get posted because of multiple notifications + // We want to ignore the other ones that happen when a task is in progress. + return; + } + Reset(); + if (!shutting_down_) { + // Check if we have work to do. + if (HavePendingTasks()) { + if (printer_delete_pending_) { + printer_delete_pending_ = false; + task_in_progress_ = true; + MakeServerRequest( + CloudPrintHelpers::GetUrlForPrinterDelete(printer_id_), + &PrinterJobHandler::HandlePrinterDeleteResponse); + } + if (!task_in_progress_ && printer_update_pending_) { + printer_update_pending_ = false; + task_in_progress_ = UpdatePrinterInfo(); + } + if (!task_in_progress_ && server_job_available_) { + task_in_progress_ = true; + server_job_available_ = false; + // We need to fetch any pending jobs for this printer + MakeServerRequest(CloudPrintHelpers::GetUrlForJobFetch(printer_id_), + &PrinterJobHandler::HandleJobMetadataResponse); + } + } + } +} + +void PrinterJobHandler::Stop() { + task_in_progress_ = false; + Reset(); + if (HavePendingTasks()) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); + } +} + +void PrinterJobHandler::NotifyJobAvailable() { + server_job_available_ = true; + if (!task_in_progress_) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); + } +} + +bool PrinterJobHandler::UpdatePrinterInfo() { + // We need to update the parts of the printer info that have changed + // (could be printer name, description, status or capabilities). + cloud_print::PrinterBasicInfo printer_info; + printer_change_notifier_.GetCurrentPrinterInfo(&printer_info); + cloud_print::PrinterCapsAndDefaults printer_caps; + std::string post_data; + std::string mime_boundary; + if (cloud_print::GetPrinterCapsAndDefaults(printer_info.printer_name, + &printer_caps)) { + std::string caps_hash = MD5String(printer_caps.printer_capabilities); + CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary); + if (caps_hash != last_caps_hash_) { + // Hashes don't match, we need to upload new capabilities (the defaults + // go for free along with the capabilities) + last_caps_hash_ = caps_hash; + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterCapsValue, printer_caps.printer_capabilities, + mime_boundary, printer_caps.caps_mime_type, &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterDefaultsValue, printer_caps.printer_defaults, + mime_boundary, printer_caps.defaults_mime_type, + &post_data); + CloudPrintHelpers::AddMultipartValueForUpload( + WideToUTF8(kPrinterCapsHashValue).c_str(), caps_hash, mime_boundary, + std::string(), &post_data); + } + } + if (printer_info.printer_name != printer_info_.printer_name) { + CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue, + printer_info.printer_name, + mime_boundary, + std::string(), &post_data); + } + if (printer_info.printer_description != printer_info_.printer_description) { + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterDescValue, printer_info.printer_description, mime_boundary, + std::string() , &post_data); + } + if (printer_info.printer_status != printer_info_.printer_status) { + CloudPrintHelpers::AddMultipartValueForUpload( + kPrinterStatusValue, StringPrintf("%d", printer_info.printer_status), + mime_boundary, std::string(), &post_data); + } + printer_info_ = printer_info; + bool ret = false; + if (!post_data.empty()) { + // Terminate the request body + post_data.append("--" + mime_boundary + "--\r\n"); + std::string mime_type("multipart/form-data; boundary="); + mime_type += mime_boundary; + request_.reset( + new URLFetcher(CloudPrintHelpers::GetUrlForPrinterUpdate(printer_id_), + URLFetcher::POST, this)); + CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); + request_->set_upload_data(mime_type, post_data); + next_response_handler_ = &PrinterJobHandler::HandlePrinterUpdateResponse; + request_->Start(); + ret = true; + } + return ret; +} + +// URLFetcher::Delegate implementation. +void PrinterJobHandler::OnURLFetchComplete( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + if (!shutting_down_) { + DCHECK(source == request_.get()); + // We need a next response handler because we are strictly a sequential + // state machine. We need each response handler to tell us which state to + // advance to next. + DCHECK(next_response_handler_); + if (!(this->*next_response_handler_)(source, url, status, + response_code, cookies, data)) { + // By contract, if the response handler returns false, it wants us to + // retry the request (upto the usual limit after which we give up and + // send the state machine to the Stop state); + HandleServerError(url); + } + } +} + +// JobStatusUpdater::Delegate implementation +bool PrinterJobHandler::OnJobCompleted(JobStatusUpdater* updater) { + bool ret = false; + for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin(); + index != job_status_updater_list_.end(); index++) { + if (index->get() == updater) { + job_status_updater_list_.erase(index); + ret = true; + break; + } + } + return ret; +} + + // cloud_print::PrinterChangeNotifier::Delegate implementation +void PrinterJobHandler::OnPrinterAdded() { + // Should never get this notification for a printer + NOTREACHED(); +} + +void PrinterJobHandler::OnPrinterDeleted() { + printer_delete_pending_ = true; + if (!task_in_progress_) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); + } +} + +void PrinterJobHandler::OnPrinterChanged() { + printer_update_pending_ = true; + if (!task_in_progress_) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start)); + } +} + +void PrinterJobHandler::OnJobChanged() { + // Some job on the printer changed. Loop through all our JobStatusUpdaters + // and have them check for updates. + for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin(); + index != job_status_updater_list_.end(); index++) { + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(index->get(), + &JobStatusUpdater::UpdateStatus)); + } +} + +bool PrinterJobHandler::HandlePrinterUpdateResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + bool ret = false; + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (status.is_success() && (response_code == 200)) { + bool succeeded = false; + DictionaryValue* response_dict = NULL; + CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); + // If we get valid JSON back, we are done. + if (NULL != response_dict) { + ret = true; + } + } + if (ret) { + // We are done here. Go to the Stop state + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); + } else { + // Since we failed to update the server, set the flag again. + printer_update_pending_ = true; + } + return ret; +} + +bool PrinterJobHandler::HandlePrinterDeleteResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + bool ret = false; + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (status.is_success() && (response_code == 200)) { + bool succeeded = false; + DictionaryValue* response_dict = NULL; + CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); + // If we get valid JSON back, we are done. + if (NULL != response_dict) { + ret = true; + } + } + if (ret) { + // The printer has been deleted. Shutdown the handler class. + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Shutdown)); + } else { + // Since we failed to update the server, set the flag again. + printer_delete_pending_ = true; + } + return ret; +} + +bool PrinterJobHandler::HandleJobMetadataResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (!status.is_success() || (response_code != 200)) { + return false; + } + bool succeeded = false; + DictionaryValue* response_dict = NULL; + CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict); + if (NULL == response_dict) { + // If we did not get a valid JSON response, we need to retry. + return false; + } + Task* next_task = NULL; + if (succeeded) { + ListValue* job_list = NULL; + response_dict->GetList(kJobListValue, &job_list); + if (job_list) { + // Even though it is a job list, for now we are only interested in the + // first job + DictionaryValue* job_data = NULL; + if (job_list->GetDictionary(0, &job_data)) { + job_data->GetString(kIdValue, &job_details_.job_id_); + job_data->GetString(kTitleValue, &job_details_.job_title_); + std::string print_ticket_url; + job_data->GetString(kTicketUrlValue, &print_ticket_url); + job_data->GetString(kFileUrlValue, &print_data_url_); + next_task = NewRunnableMethod( + this, &PrinterJobHandler::MakeServerRequest, + GURL(print_ticket_url.c_str()), + &PrinterJobHandler::HandlePrintTicketResponse); + } + } + } + if (!next_task) { + // If we got a valid JSON but there were no jobs, we are done + next_task = NewRunnableMethod(this, &PrinterJobHandler::Stop); + } + delete response_dict; + DCHECK(next_task); + MessageLoop::current()->PostTask(FROM_HERE, next_task); + return true; +} + +bool PrinterJobHandler::HandlePrintTicketResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (!status.is_success() || (response_code != 200)) { + return false; + } + if (cloud_print::ValidatePrintTicket(printer_info_.printer_name, data)) { + job_details_.print_ticket_ = data; + MessageLoop::current()->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &PrinterJobHandler::MakeServerRequest, + GURL(print_data_url_.c_str()), + &PrinterJobHandler::HandlePrintDataResponse)); + } else { + // The print ticket was not valid. We are done here. + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::JobFailed, + INVALID_JOB_DATA)); + } + return true; +} + +bool PrinterJobHandler::HandlePrintDataResponse(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (!status.is_success() || (response_code != 200)) { + return false; + } + Task* next_task = NULL; + if (file_util::CreateTemporaryFile(&job_details_.print_data_file_path_)) { + int ret = file_util::WriteFile(job_details_.print_data_file_path_, + data.c_str(), + data.length()); + source->response_headers()->GetMimeType( + &job_details_.print_data_mime_type_); + DCHECK(ret == static_cast<int>(data.length())); + if (ret == static_cast<int>(data.length())) { + next_task = NewRunnableMethod(this, &PrinterJobHandler::StartPrinting); + } + } + // If there was no task allocated above, then there was an error in + // saving the print data, bail out here. + if (!next_task) { + next_task = NewRunnableMethod(this, &PrinterJobHandler::JobFailed, + JOB_DOWNLOAD_FAILED); + } + MessageLoop::current()->PostTask(FROM_HERE, next_task); + return true; +} + +void PrinterJobHandler::StartPrinting() { + // We are done with the request object for now. + request_.reset(); + if (!shutting_down_) { + if (!print_thread_.Start()) { + JobFailed(PRINT_FAILED); + } else { + print_thread_.message_loop()->PostTask( + FROM_HERE, NewRunnableFunction(&PrinterJobHandler::DoPrint, + job_details_, + printer_info_.printer_name, this, + MessageLoop::current())); + } + } +} + +void PrinterJobHandler::JobFailed(PrintJobError error) { + if (!shutting_down_) { + UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_ERROR, error); + } +} + +void PrinterJobHandler::JobSpooled(cloud_print::PlatformJobId local_job_id) { + if (!shutting_down_) { + local_job_id_ = local_job_id; + UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_IN_PROGRESS, SUCCESS); + print_thread_.Stop(); + } +} + +void PrinterJobHandler::Shutdown() { + Reset(); + shutting_down_ = true; + while (!job_status_updater_list_.empty()) { + // Calling Stop() will cause the OnJobCompleted to be called which will + // remove the updater object from the list. + job_status_updater_list_.front()->Stop(); + } + if (delegate_) { + delegate_->OnPrinterJobHandlerShutdown(this, printer_id_); + } +} + +void PrinterJobHandler::HandleServerError(const GURL& url) { + Task* task_to_retry = NewRunnableMethod(this, + &PrinterJobHandler::MakeServerRequest, + url, next_response_handler_); + Task* task_on_give_up = NewRunnableMethod(this, &PrinterJobHandler::Stop); + CloudPrintHelpers::HandleServerError(&server_error_count_, kMaxRetryCount, + -1, kBaseRetryInterval, task_to_retry, + task_on_give_up); +} + +void PrinterJobHandler::UpdateJobStatus(cloud_print::PrintJobStatus status, + PrintJobError error) { + if (!shutting_down_) { + if (!job_details_.job_id_.empty()) { + ResponseHandler response_handler = NULL; + if (error == SUCCESS) { + response_handler = + &PrinterJobHandler::HandleSuccessStatusUpdateResponse; + } else { + response_handler = + &PrinterJobHandler::HandleFailureStatusUpdateResponse; + } + MakeServerRequest( + CloudPrintHelpers::GetUrlForJobStatusUpdate(job_details_.job_id_, + status), + response_handler); + } + } +} + +bool PrinterJobHandler::HandleSuccessStatusUpdateResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (!status.is_success() || (response_code != 200)) { + return false; + } + // The print job has been spooled locally. We now need to create an object + // that monitors the status of the job and updates the server. + scoped_refptr<JobStatusUpdater> job_status_updater = + new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_, + local_job_id_, auth_token_, this); + job_status_updater_list_.push_back(job_status_updater); + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(job_status_updater.get(), + &JobStatusUpdater::UpdateStatus)); + bool succeeded = false; + CloudPrintHelpers::ParseResponseJSON(data, &succeeded, NULL); + if (succeeded) { + // Since we just printed successfully, we want to look for more jobs. + server_job_available_ = true; + } + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); + return true; +} + +bool PrinterJobHandler::HandleFailureStatusUpdateResponse( + const URLFetcher* source, const GURL& url, const URLRequestStatus& status, + int response_code, const ResponseCookies& cookies, + const std::string& data) { + // If there was a network error or a non-200 response (which, for our purposes + // is the same as a network error), we want to retry. + if (!status.is_success() || (response_code != 200)) { + return false; + } + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop)); + return true; +} + +void PrinterJobHandler::MakeServerRequest(const GURL& url, + ResponseHandler response_handler) { + if (!shutting_down_) { + request_.reset(new URLFetcher(url, URLFetcher::GET, this)); + server_error_count_ = 0; + CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_); + // Set up the next response handler + next_response_handler_ = response_handler; + request_->Start(); + } +} + +bool PrinterJobHandler::HavePendingTasks() { + return server_job_available_ || printer_update_pending_ || + printer_delete_pending_; +} + + +void PrinterJobHandler::DoPrint(const JobDetails& job_details, + const std::string& printer_name, + PrinterJobHandler* job_handler, + MessageLoop* job_message_loop) { + DCHECK(job_handler); + DCHECK(job_message_loop); + cloud_print::PlatformJobId job_id = -1; + if (cloud_print::SpoolPrintJob(job_details.print_ticket_, + job_details.print_data_file_path_, + job_details.print_data_mime_type_, + printer_name, + job_details.job_title_, &job_id)) { + job_message_loop->PostTask(FROM_HERE, + NewRunnableMethod(job_handler, + &PrinterJobHandler::JobSpooled, + job_id)); + } else { + job_message_loop->PostTask(FROM_HERE, + NewRunnableMethod(job_handler, + &PrinterJobHandler::JobFailed, + PRINT_FAILED)); + } +} + diff --git a/chrome/browser/printing/cloud_print/printer_job_handler.h b/chrome/browser/printing/cloud_print/printer_job_handler.h new file mode 100644 index 0000000..15e5cd5 --- /dev/null +++ b/chrome/browser/printing/cloud_print/printer_job_handler.h @@ -0,0 +1,240 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRINTER_JOB_HANDLER_H_ +#define CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRINTER_JOB_HANDLER_H_ + +#include <list> +#include <string> + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "chrome/browser/printing/cloud_print/job_status_updater.h" +#include "chrome/browser/printing/cloud_print/printer_info.h" +#include "chrome/browser/net/url_fetcher.h" +#include "net/url_request/url_request_status.h" + +// A class that handles cloud print jobs for a particular printer. This class +// imlements a state machine that transitions from Start to various states. The +// various states are shown in the below diagram. +// the status on the server. + +// Start --> No pending tasks --> Done +// | +// | +// | Have Pending tasks +// | +// | +// <----Delete Pending -- | ---Update Pending-----> +// | | | +// | | | +// | | | +// Delete Printer from server | Update Printer info on server +// Shutdown | Go to Stop +// | +// | Job Available +// | +// | +// Fetch Next Job Metadata +// Fetch Print Ticket +// Fetch Print Data +// Spool Print Job +// Create Job StatusUpdater for job +// Mark job as "in progress" on server +// (On any unrecoverable error in any of the above steps go to Stop) +// Go to Stop +// | +// | +// | +// | +// | +// | +// | +// Stop +// (If there are pending tasks go back to Start) + +typedef URLFetcher::Delegate URLFetcherDelegate; + +class PrinterJobHandler : public base::RefCountedThreadSafe<PrinterJobHandler>, + public URLFetcherDelegate, + public JobStatusUpdaterDelegate, + public cloud_print::PrinterChangeNotifierDelegate { + enum PrintJobError { + SUCCESS, + JOB_DOWNLOAD_FAILED, + INVALID_JOB_DATA, + PRINT_FAILED, + }; + struct JobDetails { + std::string job_id_; + std::string job_title_; + std::string print_ticket_; + FilePath print_data_file_path_; + std::string print_data_mime_type_; + void Clear() { + job_id_.clear(); + job_title_.clear(); + print_ticket_.clear(); + print_data_mime_type_.clear(); + print_data_file_path_ = FilePath(); + } + }; + + public: + class Delegate { + public: + virtual void OnPrinterJobHandlerShutdown( + PrinterJobHandler* job_handler, const std::string& printer_id) = 0; + }; + + // Begin public interface + PrinterJobHandler(const cloud_print::PrinterBasicInfo& printer_info, + const std::string& printer_id, + const std::string& caps_hash, + const std::string& auth_token, + Delegate* delegate); + ~PrinterJobHandler(); + bool Initialize(); + // Notifies the JobHandler that a job is available + void NotifyJobAvailable(); + // Shutdown everything (the process is exiting). + void Shutdown(); + // End public interface + + // Begin Delegate implementations + + // URLFetcher::Delegate implementation. + virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + // JobStatusUpdater::Delegate implementation + virtual bool OnJobCompleted(JobStatusUpdater* updater); + // cloud_print::PrinterChangeNotifier::Delegate implementation + virtual void OnPrinterAdded(); + virtual void OnPrinterDeleted(); + virtual void OnPrinterChanged(); + virtual void OnJobChanged(); + + // End Delegate implementations + + private: + // Prototype for a response handler. The return value indicates whether the + // request should be retried, false means "retry", true means "do not retry" + typedef bool (PrinterJobHandler::*ResponseHandler)( + const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, int response_code, + const ResponseCookies& cookies, const std::string& data); + // Begin request handlers for each state in the state machine + bool HandlePrinterUpdateResponse(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + bool HandlePrinterDeleteResponse(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + bool HandleJobMetadataResponse(const URLFetcher* source, const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + bool HandlePrintTicketResponse(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + bool HandlePrintDataResponse(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + bool HandleSuccessStatusUpdateResponse(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + bool HandleFailureStatusUpdateResponse(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + // End request handlers for each state in the state machine + + // Start the state machine. Based on the flags set this could mean updating + // printer information, deleting the printer from the server or looking for + // new print jobs + void Start(); + + // End the state machine. If there are pending tasks, we will post a Start + // again. + void Stop(); + + void StartPrinting(); + void HandleServerError(const GURL& url); + void Reset(); + void UpdateJobStatus(cloud_print::PrintJobStatus status, PrintJobError error); + void MakeServerRequest(const GURL& url, ResponseHandler response_handler); + void JobFailed(PrintJobError error); + void JobSpooled(cloud_print::PlatformJobId local_job_id); + // Returns false if printer info is up to date and no updating is needed. + bool UpdatePrinterInfo(); + bool HavePendingTasks(); + + static void DoPrint(const JobDetails& job_details, + const std::string& printer_name, + PrinterJobHandler* job_handler, + MessageLoop* job_message_loop); + + + scoped_ptr<URLFetcher> request_; + cloud_print::PrinterBasicInfo printer_info_; + std::string printer_id_; + std::string auth_token_; + std::string last_caps_hash_; + std::string print_data_url_; + JobDetails job_details_; + Delegate* delegate_; + // Once the job has been spooled to the local spooler, this specifies the + // job id of the job on the local spooler. + cloud_print::PlatformJobId local_job_id_; + ResponseHandler next_response_handler_; + // The number of consecutive times that connecting to the server failed. + int server_error_count_; + // The thread on which the actual print operation happens + base::Thread print_thread_; + // There may be pending tasks in the message queue when Shutdown is called. + // We set this flag so as to do nothing in those tasks. + bool shutting_down_; + + // Flags that specify various pending server updates + bool server_job_available_; + bool printer_update_pending_; + bool printer_delete_pending_; + + // Some task in the state machine is in progress. + bool task_in_progress_; + cloud_print::PrinterChangeNotifier printer_change_notifier_; + typedef std::list< scoped_refptr<JobStatusUpdater> > JobStatusUpdaterList; + JobStatusUpdaterList job_status_updater_list_; + + DISALLOW_COPY_AND_ASSIGN(PrinterJobHandler); +}; + +// This typedef is to workaround the issue with certain versions of +// Visual Studio where it gets confused between multiple Delegate +// classes and gives a C2500 error. (I saw this error on the try bots - +// the workaround was not needed for my machine). +typedef PrinterJobHandler::Delegate PrinterJobHandlerDelegate; + +#endif // CHROME_BROWSER_PRINTING_CLOUD_PRINT_PRINTER_JOB_HANDLER_H_ + diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index 8227d23..92b2ff8 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -43,6 +43,7 @@ #include "chrome/browser/password_manager/password_store_default.h" #include "chrome/browser/password_manager/password_store_linux.h" #include "chrome/browser/privacy_blacklist/blacklist.h" +#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" #include "chrome/browser/profile_manager.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/browser/search_engines/template_url_fetcher.h" @@ -496,6 +497,10 @@ class OffTheRecordProfileImpl : public Profile, return NULL; } + virtual CloudPrintProxyService* GetCloudPrintProxyService() { + return NULL; + } + virtual bool IsSameProfile(Profile* profile) { return (profile == this) || (profile == profile_); } @@ -1448,6 +1453,12 @@ ProfileSyncService* ProfileImpl::GetProfileSyncService() { return sync_service_.get(); } +CloudPrintProxyService* ProfileImpl::GetCloudPrintProxyService() { + if (!cloud_print_proxy_service_.get()) + InitCloudPrintProxyService(); + return cloud_print_proxy_service_.get(); +} + void ProfileImpl::InitSyncService() { profile_sync_factory_.reset( new ProfileSyncFactoryImpl(this, @@ -1456,3 +1467,9 @@ void ProfileImpl::InitSyncService() { profile_sync_factory_->CreateProfileSyncService()); sync_service_->Initialize(); } + +void ProfileImpl::InitCloudPrintProxyService() { + cloud_print_proxy_service_.reset(new CloudPrintProxyService(this)); + cloud_print_proxy_service_->Initialize(); +} + diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index feee692..751d2e2 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -75,6 +75,7 @@ class VisitedLinkEventListener; class WebDataService; class WebKitContext; class WebResourceService; +class CloudPrintProxyService; typedef intptr_t ProfileId; @@ -351,6 +352,9 @@ class Profile { // Returns the ProfileSyncService, creating if not yet created. virtual ProfileSyncService* GetProfileSyncService() = 0; + // Returns the CloudPrintProxyService, creating if not yet created. + virtual CloudPrintProxyService* GetCloudPrintProxyService() = 0; + // Return whether 2 profiles are the same. 2 profiles are the same if they // represent the same profile. This can happen if there is pointer equality // or if one profile is the off the record version of another profile (or vice @@ -516,6 +520,8 @@ class ProfileImpl : public Profile, virtual NTPResourceCache* GetNTPResourceCache(); virtual ProfileSyncService* GetProfileSyncService(); void InitSyncService(); + virtual CloudPrintProxyService* GetCloudPrintProxyService(); + void InitCloudPrintProxyService(); // NotificationObserver implementation. virtual void Observe(NotificationType type, @@ -571,6 +577,7 @@ class ProfileImpl : public Profile, scoped_ptr<ProfileSyncFactory> profile_sync_factory_; scoped_ptr<ProfileSyncService> sync_service_; + scoped_ptr<CloudPrintProxyService> cloud_print_proxy_service_; scoped_refptr<ChromeURLRequestContextGetter> request_context_; diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 55a856a..f759a62 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1735,6 +1735,22 @@ 'browser/printing/print_view_manager.h', 'browser/printing/printer_query.cc', 'browser/printing/printer_query.h', + 'browser/printing/cloud_print/cloud_print_consts.cc', + 'browser/printing/cloud_print/cloud_print_consts.h', + 'browser/printing/cloud_print/cloud_print_helpers.cc', + 'browser/printing/cloud_print/cloud_print_helpers.h', + 'browser/printing/cloud_print/cloud_print_proxy_backend.cc', + 'browser/printing/cloud_print/cloud_print_proxy_backend.h', + 'browser/printing/cloud_print/cloud_print_proxy_service.cc', + 'browser/printing/cloud_print/cloud_print_proxy_service.h', + 'browser/printing/cloud_print/job_status_updater.cc', + 'browser/printing/cloud_print/job_status_updater.h', + 'browser/printing/cloud_print/printer_info_linux.cc', + 'browser/printing/cloud_print/printer_info_mac.cc', + 'browser/printing/cloud_print/printer_info_win.cc', + 'browser/printing/cloud_print/printer_info.h', + 'browser/printing/cloud_print/printer_job_handler.cc', + 'browser/printing/cloud_print/printer_job_handler.h', 'browser/privacy_blacklist/blacklist.h', 'browser/privacy_blacklist/blacklist.cc', 'browser/privacy_blacklist/blacklist_interceptor.h', diff --git a/chrome/chrome_dll.gypi b/chrome/chrome_dll.gypi index ac5845f..79f291f 100644 --- a/chrome/chrome_dll.gypi +++ b/chrome/chrome_dll.gypi @@ -35,6 +35,7 @@ 'urlmon.dll', 'imm32.dll', 'iphlpapi.dll', + 'prntvpt.dll', ], # Set /SUBSYSTEM:WINDOWS for chrome.dll (for consistency). 'SubSystem': '2', diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 4f0e23c..2670eec 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -752,4 +752,10 @@ const wchar_t kGeolocationAccessToken[] = L"geolocation.access_token"; // LoginDatabase. const wchar_t kLoginDatabaseMigrated[] = L"login_database.migrated"; +// The unique id for this instance of the cloud print proxy. +const wchar_t kCloudPrintProxyId[] = L"cloud_print.proxy_id"; +// The human-readable name of the Cloud Print proxy. User-supplied. +const wchar_t kCloudPrintProxyName[] = L"cloud_print.proxy_name"; +// The GAIA auth token (we may need to move it outside of prefs) +const wchar_t kCloudPrintAuthToken[] = L"cloud_print_auth_token"; } // namespace prefs diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index ebb6658..1201f94 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -277,6 +277,10 @@ extern const wchar_t kGeolocationContentSettings[]; extern const wchar_t kLoginDatabaseMigrated[]; +extern const wchar_t kCloudPrintProxyId[]; +extern const wchar_t kCloudPrintProxyName[]; +extern const wchar_t kCloudPrintAuthToken[]; + } // namespace prefs #endif // CHROME_COMMON_PREF_NAMES_H_ diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h index 814d8b9..b4e97d5 100644 --- a/chrome/test/testing_profile.h +++ b/chrome/test/testing_profile.h @@ -255,6 +255,7 @@ class TestingProfile : public Profile { // Creates and initializes a profile sync service if the tests require one. virtual ProfileSyncService* GetProfileSyncService(); + virtual CloudPrintProxyService* GetCloudPrintProxyService() { return NULL; } protected: // The path of the profile; the various database and other files are relative |