diff options
author | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-13 17:18:01 +0000 |
---|---|---|
committer | sanjeevr@chromium.org <sanjeevr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-13 17:18:01 +0000 |
commit | 8b50bd202626cf978685197adee0d8f32161443b (patch) | |
tree | fe543b2bba72103859baefb8e149814e3e35e2ee /chrome/service/cloud_print | |
parent | d94ccedd108876d5be652dfa8129df54cab555e9 (diff) | |
download | chromium_src-8b50bd202626cf978685197adee0d8f32161443b.zip chromium_src-8b50bd202626cf978685197adee0d8f32161443b.tar.gz chromium_src-8b50bd202626cf978685197adee0d8f32161443b.tar.bz2 |
Moved cloud print code from the chrome/browser/printing/cloud_print to chrome/service/cloud_print
BUG=None.
TEST=None.
Review URL: http://codereview.chromium.org/2007012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@47155 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/service/cloud_print')
-rw-r--r-- | chrome/service/cloud_print/cloud_print_consts.cc | 36 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_consts.h | 41 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_helpers.cc | 159 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_helpers.h | 71 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_proxy.cc | 20 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_proxy.h | 13 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_proxy_backend.cc | 551 | ||||
-rw-r--r-- | chrome/service/cloud_print/cloud_print_proxy_backend.h | 70 | ||||
-rw-r--r-- | chrome/service/cloud_print/job_status_updater.cc | 90 | ||||
-rw-r--r-- | chrome/service/cloud_print/job_status_updater.h | 64 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_info.h | 125 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_info_linux.cc | 80 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_info_mac.cc | 80 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_info_win.cc | 509 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_job_handler.cc | 560 | ||||
-rw-r--r-- | chrome/service/cloud_print/printer_job_handler.h | 240 |
16 files changed, 2705 insertions, 4 deletions
diff --git a/chrome/service/cloud_print/cloud_print_consts.cc b/chrome/service/cloud_print/cloud_print_consts.cc new file mode 100644 index 0000000..d1f6c17 --- /dev/null +++ b/chrome/service/cloud_print/cloud_print_consts.cc @@ -0,0 +1,36 @@ +// 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/service/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[] = "http://<TBD>"; +// TODO(sanjeevr): Change this to a real one. +const char kCloudPrintTalkServiceUrl[] = "http://www.google.com/printing"; +const char kGaiaUrl[] = "https://www.google.com/accounts/ClientLogin"; +// TODO(sanjeevr): Change this to a real one once we get a GAIA service id. +const char kCloudPrintGaiaServiceId[] = "print"; +const char kSyncGaiaServiceId[] = "chromiumsync"; + diff --git a/chrome/service/cloud_print/cloud_print_consts.h b/chrome/service/cloud_print/cloud_print_consts.h new file mode 100644 index 0000000..126f58e --- /dev/null +++ b/chrome/service/cloud_print/cloud_print_consts.h @@ -0,0 +1,41 @@ +// 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_SERVICE_CLOUD_PRINT_CLOUD_PRINT_CONSTS_H_ +#define CHROME_SERVICE_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[]; +extern const char kCloudPrintTalkServiceUrl[]; +extern const char kGaiaUrl[]; +extern const char kCloudPrintGaiaServiceId[]; +extern const char kSyncGaiaServiceId[]; + +// 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_SERVICE_CLOUD_PRINT_CLOUD_PRINT_CONSTS_H_ + diff --git a/chrome/service/cloud_print/cloud_print_helpers.cc b/chrome/service/cloud_print/cloud_print_helpers.cc new file mode 100644 index 0000000..85d02ad --- /dev/null +++ b/chrome/service/cloud_print/cloud_print_helpers.cc @@ -0,0 +1,159 @@ +// 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/service/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/service/cloud_print/cloud_print_consts.h" +#include "chrome/common/net/url_fetcher.h" +#include "chrome/service/net/service_url_request_context.h" +#include "chrome/service/service_process.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)); + if (!message_value.get()) { + NOTREACHED(); + return false; + } + if (!message_value->IsType(Value::TYPE_DICTIONARY)) { + NOTREACHED(); + return false; + } + scoped_ptr<DictionaryValue> response_dict_local( + static_cast<DictionaryValue*>(message_value.release())); + if (succeeded) + response_dict_local->GetBoolean(kSuccessValue, succeeded); + if (response_dict) + *response_dict = response_dict_local.release(); + return true; +} + +void CloudPrintHelpers::PrepCloudPrintRequest(URLFetcher* request, + const std::string& auth_token) { + DCHECK(g_service_process); + request->set_request_context(new ServiceURLRequestContextGetter()); + 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/service/cloud_print/cloud_print_helpers.h b/chrome/service/cloud_print/cloud_print_helpers.h new file mode 100644 index 0000000..1815663 --- /dev/null +++ b/chrome/service/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_SERVICE_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_ +#define CHROME_SERVICE_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_ + +#include <string> + +#include "chrome/service/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_SERVICE_CLOUD_PRINT_CLOUD_PRINT_HELPERS_H_ + diff --git a/chrome/service/cloud_print/cloud_print_proxy.cc b/chrome/service/cloud_print/cloud_print_proxy.cc index ea6139b..aff851a 100644 --- a/chrome/service/cloud_print/cloud_print_proxy.cc +++ b/chrome/service/cloud_print/cloud_print_proxy.cc @@ -17,6 +17,11 @@ void CloudPrintProxy::Initialize() { void CloudPrintProxy::EnableForUser(const std::string& lsid, const std::string& proxy_id) { + if (backend_.get()) + return; + + backend_.reset(new CloudPrintProxyBackend(this)); + backend_->Initialize(lsid, proxy_id); } void CloudPrintProxy::DisableForUser() { @@ -25,8 +30,23 @@ void CloudPrintProxy::DisableForUser() { void CloudPrintProxy::HandlePrinterNotification( const std::string& printer_id) { + if (backend_.get()) + backend_->HandlePrinterNotification(printer_id); } void CloudPrintProxy::Shutdown() { + if (backend_.get()) + backend_->Shutdown(); + backend_.reset(); +} + +// Notification methods from the backend. Called on UI thread. +void CloudPrintProxy::OnPrinterListAvailable( + const cloud_print::PrinterList& printer_list) { + // 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); } diff --git a/chrome/service/cloud_print/cloud_print_proxy.h b/chrome/service/cloud_print/cloud_print_proxy.h index 0f67d7f..e76f0d9 100644 --- a/chrome/service/cloud_print/cloud_print_proxy.h +++ b/chrome/service/cloud_print/cloud_print_proxy.h @@ -9,13 +9,11 @@ #include "base/basictypes.h" #include "base/scoped_ptr.h" - -// TODO(sanjeevr): Integrate this with the CloudPrintProxyBackend. This needs to -// happen after the cloud_print related files are moved to chrome/service. +#include "chrome/service/cloud_print/cloud_print_proxy_backend.h" // CloudPrintProxy is the layer between the service process UI thread // and the cloud print proxy backend. -class CloudPrintProxy { +class CloudPrintProxy : public CloudPrintProxyFrontend { public: explicit CloudPrintProxy(); virtual ~CloudPrintProxy(); @@ -33,9 +31,16 @@ class CloudPrintProxy { // 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); + protected: void Shutdown(); + // Our asynchronous backend to communicate with sync components living on + // other threads. + scoped_ptr<CloudPrintProxyBackend> backend_; + DISALLOW_COPY_AND_ASSIGN(CloudPrintProxy); }; diff --git a/chrome/service/cloud_print/cloud_print_proxy_backend.cc b/chrome/service/cloud_print/cloud_print_proxy_backend.cc new file mode 100644 index 0000000..4b0616c --- /dev/null +++ b/chrome/service/cloud_print/cloud_print_proxy_backend.cc @@ -0,0 +1,551 @@ +// 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/service/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/service/cloud_print/cloud_print_consts.h" +#include "chrome/service/cloud_print/cloud_print_helpers.h" +#include "chrome/service/cloud_print/printer_job_handler.h" +#include "chrome/common/deprecated/event_sys-inl.h" +#include "chrome/common/net/notifier/listener/talk_mediator_impl.h" +#include "chrome/service/gaia/service_gaia_authenticator.h" +#include "chrome/service/service_process.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& lsid, 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); + void HandleTalkMediatorEvent(const notifier::TalkMediatorEvent& event); + + // 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_; + // Notification (xmpp) handler. + scoped_ptr<notifier::TalkMediator> talk_mediator_; + scoped_ptr<EventListenerHookup> talk_mediator_hookup_; + + 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& lsid, + const std::string& proxy_id) { + if (!core_thread_.Start()) + return false; + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod( + core_.get(), &CloudPrintProxyBackend::Core::DoInitialize, lsid, + 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& lsid, + const std::string& proxy_id) { + DCHECK(MessageLoop::current() == backend_->core_thread_.message_loop()); + // Since Talk does not accept a Cloud Print token, for now, we make 2 auth + // requests, one for the chromiumsync service and another for print. This is + // temporary and should be removed once Talk supports our token. + // Note: The GAIA login is synchronous but that should be OK because we are in + // the CloudPrintProxyCoreThread and we cannot really do anything else until + // the GAIA signin is successful. + std::string user_agent = "ChromiumBrowser"; + scoped_refptr<ServiceGaiaAuthenticator> gaia_auth_for_talk = + new ServiceGaiaAuthenticator( + user_agent, kSyncGaiaServiceId, kGaiaUrl, + g_service_process->io_thread()->message_loop_proxy()); + gaia_auth_for_talk->set_message_loop(MessageLoop::current()); + // TODO(sanjeevr): Handle auth failure case. We basically need to disable + // cloud print and shutdown. + if (gaia_auth_for_talk->AuthenticateWithLsid(lsid, true)) { + scoped_refptr<ServiceGaiaAuthenticator> gaia_auth_for_print = + new ServiceGaiaAuthenticator( + user_agent, kCloudPrintGaiaServiceId, kGaiaUrl, + g_service_process->io_thread()->message_loop_proxy()); + gaia_auth_for_print->set_message_loop(MessageLoop::current()); + if (gaia_auth_for_print->AuthenticateWithLsid(lsid, true)) { + auth_token_ = gaia_auth_for_print->auth_token(); + talk_mediator_.reset( + new notifier::TalkMediatorImpl(false)); + talk_mediator_->AddSubscribedServiceUrl(kCloudPrintTalkServiceUrl); + talk_mediator_hookup_.reset( + NewEventListenerHookup( + talk_mediator_->channel(), + this, + &CloudPrintProxyBackend::Core::HandleTalkMediatorEvent)); + talk_mediator_->SetAuthToken(gaia_auth_for_talk->email(), + gaia_auth_for_talk->auth_token(), + kSyncGaiaServiceId); + talk_mediator_->Login(); + } + } + printer_change_notifier_.StartWatching(std::string(), this); + proxy_id_ = proxy_id; + 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; +} + +void CloudPrintProxyBackend::Core::HandleTalkMediatorEvent( + const notifier::TalkMediatorEvent& event) { + if ((event.what_happened == + notifier::TalkMediatorEvent::NOTIFICATION_RECEIVED) && + (0 == base::strcasecmp(kCloudPrintTalkServiceUrl, + event.notification_data.service_url.c_str()))) { + backend_->core_thread_.message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &CloudPrintProxyBackend::Core::DoHandlePrinterNotification, + event.notification_data.service_specific_data)); + } +} + +// 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/service/cloud_print/cloud_print_proxy_backend.h b/chrome/service/cloud_print/cloud_print_proxy_backend.h new file mode 100644 index 0000000..d622ccc --- /dev/null +++ b/chrome/service/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_SERVICE_CLOUD_PRINT_CLOUD_PRINT_PROXY_BACKEND_H_ +#define CHROME_SERVICE_CLOUD_PRINT_CLOUD_PRINT_PROXY_BACKEND_H_ + +#include <map> +#include <string> + +#include "base/thread.h" +#include "chrome/service/cloud_print/printer_info.h" +#include "chrome/common/net/url_fetcher.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& lsid, 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_SERVICE_CLOUD_PRINT_CLOUD_PRINT_PROXY_BACKEND_H_ + diff --git a/chrome/service/cloud_print/job_status_updater.cc b/chrome/service/cloud_print/job_status_updater.cc new file mode 100644 index 0000000..2492a84 --- /dev/null +++ b/chrome/service/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/service/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/service/cloud_print/cloud_print_consts.h" +#include "chrome/service/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/service/cloud_print/job_status_updater.h b/chrome/service/cloud_print/job_status_updater.h new file mode 100644 index 0000000..d840b1c --- /dev/null +++ b/chrome/service/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_SERVICE_CLOUD_PRINT_JOB_STATUS_UPDATER_H_ +#define CHROME_SERVICE_CLOUD_PRINT_JOB_STATUS_UPDATER_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "base/thread.h" +#include "chrome/service/cloud_print/printer_info.h" +#include "chrome/common/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_SERVICE_CLOUD_PRINT_JOB_STATUS_UPDATER_H_ + diff --git a/chrome/service/cloud_print/printer_info.h b/chrome/service/cloud_print/printer_info.h new file mode 100644 index 0000000..912e6bf --- /dev/null +++ b/chrome/service/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_SERVICE_CLOUD_PRINT_PRINTER_INFO_H_ +#define CHROME_SERVICE_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_SERVICE_CLOUD_PRINT_PRINTER_INFO_H_ + diff --git a/chrome/service/cloud_print/printer_info_linux.cc b/chrome/service/cloud_print/printer_info_linux.cc new file mode 100644 index 0000000..7ef9529 --- /dev/null +++ b/chrome/service/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/service/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/service/cloud_print/printer_info_mac.cc b/chrome/service/cloud_print/printer_info_mac.cc new file mode 100644 index 0000000..1c09b4a --- /dev/null +++ b/chrome/service/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/service/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/service/cloud_print/printer_info_win.cc b/chrome/service/cloud_print/printer_info_win.cc new file mode 100644 index 0000000..4839d60 --- /dev/null +++ b/chrome/service/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/service/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/service/cloud_print/printer_job_handler.cc b/chrome/service/cloud_print/printer_job_handler.cc new file mode 100644 index 0000000..09b649f --- /dev/null +++ b/chrome/service/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/service/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/service/cloud_print/cloud_print_consts.h" +#include "chrome/service/cloud_print/cloud_print_helpers.h" +#include "chrome/service/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/service/cloud_print/printer_job_handler.h b/chrome/service/cloud_print/printer_job_handler.h new file mode 100644 index 0000000..5b480db --- /dev/null +++ b/chrome/service/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_SERVICE_CLOUD_PRINT_PRINTER_JOB_HANDLER_H_ +#define CHROME_SERVICE_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/service/cloud_print/job_status_updater.h" +#include "chrome/service/cloud_print/printer_info.h" +#include "chrome/common/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_SERVICE_CLOUD_PRINT_PRINTER_JOB_HANDLER_H_ + |