• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/service/cloud_print/printer_job_handler.h"
6 
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/json/json_reader.h"
11 #include "base/md5.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/common/cloud_print/cloud_print_constants.h"
17 #include "chrome/common/cloud_print/cloud_print_helpers.h"
18 #include "chrome/service/cloud_print/cloud_print_service_helpers.h"
19 #include "chrome/service/cloud_print/job_status_updater.h"
20 #include "grit/generated_resources.h"
21 #include "net/base/mime_util.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_status_code.h"
24 #include "printing/printing_utils.h"
25 #include "ui/base/l10n/l10n_util.h"
26 #include "url/gurl.h"
27 
28 namespace cloud_print {
29 
30 namespace {
31 
32 base::subtle::Atomic32 g_total_jobs_started = 0;
33 base::subtle::Atomic32 g_total_jobs_done = 0;
34 
35 enum PrinterJobHandlerEvent {
36   JOB_HANDLER_CHECK_FOR_JOBS,
37   JOB_HANDLER_START,
38   JOB_HANDLER_PENDING_TASK,
39   JOB_HANDLER_PRINTER_UPDATE,
40   JOB_HANDLER_JOB_CHECK,
41   JOB_HANDLER_JOB_STARTED,
42   JOB_HANDLER_VALID_TICKET,
43   JOB_HANDLER_DATA,
44   JOB_HANDLER_SET_IN_PROGRESS,
45   JOB_HANDLER_SET_START_PRINTING,
46   JOB_HANDLER_START_SPOOLING,
47   JOB_HANDLER_SPOOLED,
48   JOB_HANDLER_JOB_COMPLETED,
49   JOB_HANDLER_INVALID_TICKET,
50   JOB_HANDLER_INVALID_DATA,
51   JOB_HANDLER_MAX,
52 };
53 
54 }  // namespace
55 
PrinterInfoFromCloud()56 PrinterJobHandler::PrinterInfoFromCloud::PrinterInfoFromCloud()
57     : current_xmpp_timeout(0), pending_xmpp_timeout(0) {
58 }
59 
PrinterJobHandler(const printing::PrinterBasicInfo & printer_info,const PrinterInfoFromCloud & printer_info_cloud,const GURL & cloud_print_server_url,PrintSystem * print_system,Delegate * delegate)60 PrinterJobHandler::PrinterJobHandler(
61     const printing::PrinterBasicInfo& printer_info,
62     const PrinterInfoFromCloud& printer_info_cloud,
63     const GURL& cloud_print_server_url,
64     PrintSystem* print_system,
65     Delegate* delegate)
66     : print_system_(print_system),
67       printer_info_(printer_info),
68       printer_info_cloud_(printer_info_cloud),
69       cloud_print_server_url_(cloud_print_server_url),
70       delegate_(delegate),
71       local_job_id_(-1),
72       next_json_data_handler_(NULL),
73       next_data_handler_(NULL),
74       server_error_count_(0),
75       print_thread_("Chrome_CloudPrintJobPrintThread"),
76       job_handler_message_loop_proxy_(
77           base::MessageLoopProxy::current()),
78       shutting_down_(false),
79       job_check_pending_(false),
80       printer_update_pending_(true),
81       task_in_progress_(false),
82       weak_ptr_factory_(this) {
83 }
84 
Initialize()85 bool PrinterJobHandler::Initialize() {
86   if (!print_system_->IsValidPrinter(printer_info_.printer_name))
87     return false;
88 
89   printer_watcher_ = print_system_->CreatePrinterWatcher(
90       printer_info_.printer_name);
91   printer_watcher_->StartWatching(this);
92   CheckForJobs(kJobFetchReasonStartup);
93   return true;
94 }
95 
GetPrinterName() const96 std::string PrinterJobHandler::GetPrinterName() const {
97   return printer_info_.printer_name;
98 }
99 
CheckForJobs(const std::string & reason)100 void PrinterJobHandler::CheckForJobs(const std::string& reason) {
101   VLOG(1) << "CP_CONNECTOR: Checking for jobs"
102           << ", printer id: " << printer_info_cloud_.printer_id
103           << ", reason: " << reason
104           << ", task in progress: " << task_in_progress_;
105   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
106                             JOB_HANDLER_CHECK_FOR_JOBS, JOB_HANDLER_MAX);
107   job_fetch_reason_ = reason;
108   job_check_pending_ = true;
109   if (!task_in_progress_) {
110     base::MessageLoop::current()->PostTask(
111         FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
112   }
113 }
114 
Shutdown()115 void PrinterJobHandler::Shutdown() {
116   VLOG(1) << "CP_CONNECTOR: Shutting down printer job handler"
117           << ", printer id: " << printer_info_cloud_.printer_id;
118   Reset();
119   shutting_down_ = true;
120   while (!job_status_updater_list_.empty()) {
121     // Calling Stop() will cause the OnJobCompleted to be called which will
122     // remove the updater object from the list.
123     job_status_updater_list_.front()->Stop();
124   }
125 }
126 
127 // CloudPrintURLFetcher::Delegate implementation.
HandleRawResponse(const net::URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const net::ResponseCookies & cookies,const std::string & data)128 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawResponse(
129     const net::URLFetcher* source,
130     const GURL& url,
131     const net::URLRequestStatus& status,
132     int response_code,
133     const net::ResponseCookies& cookies,
134     const std::string& data) {
135   // 415 (Unsupported media type) error while fetching data from the server
136   // means data conversion error. Stop fetching process and mark job as error.
137   if (next_data_handler_ == (&PrinterJobHandler::HandlePrintDataResponse) &&
138       response_code == net::HTTP_UNSUPPORTED_MEDIA_TYPE) {
139     VLOG(1) << "CP_CONNECTOR: Job failed (unsupported media type)";
140     base::MessageLoop::current()->PostTask(
141         FROM_HERE,
142         base::Bind(&PrinterJobHandler::JobFailed, this, JOB_DOWNLOAD_FAILED));
143     return CloudPrintURLFetcher::STOP_PROCESSING;
144   }
145   return CloudPrintURLFetcher::CONTINUE_PROCESSING;
146 }
147 
HandleRawData(const net::URLFetcher * source,const GURL & url,const std::string & data)148 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleRawData(
149     const net::URLFetcher* source,
150     const GURL& url,
151     const std::string& data) {
152   if (!next_data_handler_)
153     return CloudPrintURLFetcher::CONTINUE_PROCESSING;
154   return (this->*next_data_handler_)(source, url, data);
155 }
156 
HandleJSONData(const net::URLFetcher * source,const GURL & url,DictionaryValue * json_data,bool succeeded)157 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::HandleJSONData(
158     const net::URLFetcher* source,
159     const GURL& url,
160     DictionaryValue* json_data,
161     bool succeeded) {
162   DCHECK(next_json_data_handler_);
163   return (this->*next_json_data_handler_)(source, url, json_data, succeeded);
164 }
165 
166 // Mark the job fetch as failed and check if other jobs can be printed
OnRequestGiveUp()167 void PrinterJobHandler::OnRequestGiveUp() {
168   if (job_queue_handler_.JobFetchFailed(job_details_.job_id_)) {
169     VLOG(1) << "CP_CONNECTOR: Job failed to load (scheduling retry)";
170     CheckForJobs(kJobFetchReasonFailure);
171     base::MessageLoop::current()->PostTask(
172         FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
173   } else {
174     VLOG(1) << "CP_CONNECTOR: Job failed (giving up after " <<
175         kNumRetriesBeforeAbandonJob << " retries)";
176     base::MessageLoop::current()->PostTask(
177         FROM_HERE,
178         base::Bind(&PrinterJobHandler::JobFailed, this, JOB_DOWNLOAD_FAILED));
179   }
180 }
181 
OnRequestAuthError()182 CloudPrintURLFetcher::ResponseAction PrinterJobHandler::OnRequestAuthError() {
183   // We got an Auth error and have no idea how long it will take to refresh
184   // auth information (may take forever). We'll drop current request and
185   // propagate this error to the upper level. After auth issues will be
186   // resolved, GCP connector will restart.
187   OnAuthError();
188   return CloudPrintURLFetcher::STOP_PROCESSING;
189 }
190 
GetAuthHeader()191 std::string PrinterJobHandler::GetAuthHeader() {
192   return GetCloudPrintAuthHeaderFromStore();
193 }
194 
195 // JobStatusUpdater::Delegate implementation
OnJobCompleted(JobStatusUpdater * updater)196 bool PrinterJobHandler::OnJobCompleted(JobStatusUpdater* updater) {
197   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
198                             JOB_HANDLER_JOB_COMPLETED, JOB_HANDLER_MAX);
199   UMA_HISTOGRAM_LONG_TIMES("CloudPrint.PrintingTime",
200                            base::Time::Now() - updater->start_time());
201   bool ret = false;
202   base::subtle::NoBarrier_AtomicIncrement(&g_total_jobs_done, 1);
203   job_queue_handler_.JobDone(job_details_.job_id_);
204 
205   for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
206        index != job_status_updater_list_.end(); index++) {
207     if (index->get() == updater) {
208       job_status_updater_list_.erase(index);
209       ret = true;
210       break;
211     }
212   }
213   return ret;
214 }
215 
OnAuthError()216 void PrinterJobHandler::OnAuthError() {
217   base::MessageLoop::current()->PostTask(
218       FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
219   if (delegate_)
220     delegate_->OnAuthError();
221 }
222 
OnPrinterDeleted()223 void PrinterJobHandler::OnPrinterDeleted() {
224   if (delegate_)
225     delegate_->OnPrinterDeleted(printer_info_cloud_.printer_id);
226 }
227 
OnPrinterChanged()228 void PrinterJobHandler::OnPrinterChanged() {
229   printer_update_pending_ = true;
230   if (!task_in_progress_) {
231     base::MessageLoop::current()->PostTask(
232         FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
233   }
234 }
235 
OnJobChanged()236 void PrinterJobHandler::OnJobChanged() {
237   // Some job on the printer changed. Loop through all our JobStatusUpdaters
238   // and have them check for updates.
239   for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
240        index != job_status_updater_list_.end(); index++) {
241     base::MessageLoop::current()->PostTask(
242         FROM_HERE, base::Bind(&JobStatusUpdater::UpdateStatus, index->get()));
243   }
244 }
245 
OnJobSpoolSucceeded(const PlatformJobId & job_id)246 void PrinterJobHandler::OnJobSpoolSucceeded(const PlatformJobId& job_id) {
247   DCHECK(base::MessageLoop::current() == print_thread_.message_loop());
248   job_spooler_->AddRef();
249   print_thread_.message_loop()->ReleaseSoon(FROM_HERE, job_spooler_.get());
250   job_spooler_ = NULL;
251   job_handler_message_loop_proxy_->PostTask(
252       FROM_HERE, base::Bind(&PrinterJobHandler::JobSpooled, this, job_id));
253 }
254 
OnJobSpoolFailed()255 void PrinterJobHandler::OnJobSpoolFailed() {
256   DCHECK(base::MessageLoop::current() == print_thread_.message_loop());
257   job_spooler_->AddRef();
258   print_thread_.message_loop()->ReleaseSoon(FROM_HERE, job_spooler_.get());
259   job_spooler_ = NULL;
260   VLOG(1) << "CP_CONNECTOR: Job failed (spool failed)";
261   job_handler_message_loop_proxy_->PostTask(
262       FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this, JOB_FAILED));
263 }
264 
265 // static
ReportsStats()266 void PrinterJobHandler::ReportsStats() {
267   base::subtle::Atomic32 started =
268       base::subtle::NoBarrier_AtomicExchange(&g_total_jobs_started, 0);
269   base::subtle::Atomic32 done =
270       base::subtle::NoBarrier_AtomicExchange(&g_total_jobs_done, 0);
271   UMA_HISTOGRAM_COUNTS_100("CloudPrint.JobsStartedPerInterval", started);
272   UMA_HISTOGRAM_COUNTS_100("CloudPrint.JobsDonePerInterval", done);
273 }
274 
~PrinterJobHandler()275 PrinterJobHandler::~PrinterJobHandler() {
276   if (printer_watcher_.get())
277     printer_watcher_->StopWatching();
278 }
279 
280 // Begin Response handlers
281 CloudPrintURLFetcher::ResponseAction
HandlePrinterUpdateResponse(const net::URLFetcher * source,const GURL & url,DictionaryValue * json_data,bool succeeded)282 PrinterJobHandler::HandlePrinterUpdateResponse(
283     const net::URLFetcher* source,
284     const GURL& url,
285     DictionaryValue* json_data,
286     bool succeeded) {
287   VLOG(1) << "CP_CONNECTOR: Handling printer update response"
288           << ", printer id: " << printer_info_cloud_.printer_id;
289   // We are done here. Go to the Stop state
290   VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
291           << ", printer id: " << printer_info_cloud_.printer_id;
292   base::MessageLoop::current()->PostTask(
293       FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
294   return CloudPrintURLFetcher::STOP_PROCESSING;
295 }
296 
297 CloudPrintURLFetcher::ResponseAction
HandleJobMetadataResponse(const net::URLFetcher * source,const GURL & url,DictionaryValue * json_data,bool succeeded)298 PrinterJobHandler::HandleJobMetadataResponse(
299     const net::URLFetcher* source,
300     const GURL& url,
301     DictionaryValue* json_data,
302     bool succeeded) {
303   VLOG(1) << "CP_CONNECTOR: Handling job metadata response"
304           << ", printer id: " << printer_info_cloud_.printer_id;
305   bool job_available = false;
306   if (succeeded) {
307     std::vector<JobDetails> jobs;
308     job_queue_handler_.GetJobsFromQueue(json_data, &jobs);
309     if (!jobs.empty()) {
310       if (jobs[0].time_remaining_ == base::TimeDelta()) {
311         job_available = true;
312         job_details_ = jobs[0];
313         job_start_time_ = base::Time::Now();
314         base::subtle::NoBarrier_AtomicIncrement(&g_total_jobs_started, 1);
315         UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
316                                   JOB_HANDLER_JOB_STARTED, JOB_HANDLER_MAX);
317         SetNextDataHandler(&PrinterJobHandler::HandlePrintTicketResponse);
318         request_ = CloudPrintURLFetcher::Create();
319         request_->StartGetRequest(CloudPrintURLFetcher::REQUEST_TICKET,
320             GURL(job_details_.print_ticket_url_), this, kJobDataMaxRetryCount,
321             std::string());
322       } else {
323         job_available = false;
324         base::MessageLoop::current()->PostDelayedTask(
325             FROM_HERE,
326             base::Bind(&PrinterJobHandler::RunScheduledJobCheck, this),
327             jobs[0].time_remaining_);
328       }
329     }
330   }
331 
332   if (!job_available) {
333     // If no jobs are available, go to the Stop state.
334     VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
335             << ", printer id: " << printer_info_cloud_.printer_id;
336     base::MessageLoop::current()->PostTask(
337         FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
338   }
339   return CloudPrintURLFetcher::STOP_PROCESSING;
340 }
341 
342 CloudPrintURLFetcher::ResponseAction
HandlePrintTicketResponse(const net::URLFetcher * source,const GURL & url,const std::string & data)343 PrinterJobHandler::HandlePrintTicketResponse(const net::URLFetcher* source,
344                                              const GURL& url,
345                                              const std::string& data) {
346   VLOG(1) << "CP_CONNECTOR: Handling print ticket response"
347           << ", printer id: " << printer_info_cloud_.printer_id;
348   if (print_system_->ValidatePrintTicket(printer_info_.printer_name, data)) {
349     UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
350                               JOB_HANDLER_VALID_TICKET, JOB_HANDLER_MAX);
351     job_details_.print_ticket_ = data;
352     SetNextDataHandler(&PrinterJobHandler::HandlePrintDataResponse);
353     request_ = CloudPrintURLFetcher::Create();
354     std::string accept_headers = "Accept: ";
355     accept_headers += print_system_->GetSupportedMimeTypes();
356     request_->StartGetRequest(CloudPrintURLFetcher::REQUEST_DATA,
357         GURL(job_details_.print_data_url_), this, kJobDataMaxRetryCount,
358         accept_headers);
359   } else {
360     UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
361                               JOB_HANDLER_INVALID_TICKET, JOB_HANDLER_MAX);
362     // The print ticket was not valid. We are done here.
363     ValidatePrintTicketFailed();
364   }
365   return CloudPrintURLFetcher::STOP_PROCESSING;
366 }
367 
368 CloudPrintURLFetcher::ResponseAction
HandlePrintDataResponse(const net::URLFetcher * source,const GURL & url,const std::string & data)369 PrinterJobHandler::HandlePrintDataResponse(const net::URLFetcher* source,
370                                            const GURL& url,
371                                            const std::string& data) {
372   VLOG(1) << "CP_CONNECTOR: Handling print data response"
373           << ", printer id: " << printer_info_cloud_.printer_id;
374   if (base::CreateTemporaryFile(&job_details_.print_data_file_path_)) {
375     UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", JOB_HANDLER_DATA,
376                               JOB_HANDLER_MAX);
377     int ret = file_util::WriteFile(job_details_.print_data_file_path_,
378                                    data.c_str(), data.length());
379     source->GetResponseHeaders()->GetMimeType(
380         &job_details_.print_data_mime_type_);
381     DCHECK(ret == static_cast<int>(data.length()));
382     if (ret == static_cast<int>(data.length())) {
383       UpdateJobStatus(PRINT_JOB_STATUS_IN_PROGRESS, JOB_SUCCESS);
384       return CloudPrintURLFetcher::STOP_PROCESSING;
385     }
386   }
387   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
388                             JOB_HANDLER_INVALID_DATA, JOB_HANDLER_MAX);
389 
390   // If we are here, then there was an error in saving the print data, bail out
391   // here.
392   VLOG(1) << "CP_CONNECTOR: Error saving print data"
393           << ", printer id: " << printer_info_cloud_.printer_id;
394   base::MessageLoop::current()->PostTask(
395       FROM_HERE, base::Bind(&PrinterJobHandler::JobFailed, this,
396                             JOB_DOWNLOAD_FAILED));
397   return CloudPrintURLFetcher::STOP_PROCESSING;
398 }
399 
400 CloudPrintURLFetcher::ResponseAction
HandleInProgressStatusUpdateResponse(const net::URLFetcher * source,const GURL & url,DictionaryValue * json_data,bool succeeded)401 PrinterJobHandler::HandleInProgressStatusUpdateResponse(
402     const net::URLFetcher* source,
403     const GURL& url,
404     DictionaryValue* json_data,
405     bool succeeded) {
406   VLOG(1) << "CP_CONNECTOR: Handling success status update response"
407           << ", printer id: " << printer_info_cloud_.printer_id;
408   base::MessageLoop::current()->PostTask(
409       FROM_HERE, base::Bind(&PrinterJobHandler::StartPrinting, this));
410   return CloudPrintURLFetcher::STOP_PROCESSING;
411 }
412 
413 CloudPrintURLFetcher::ResponseAction
HandleFailureStatusUpdateResponse(const net::URLFetcher * source,const GURL & url,DictionaryValue * json_data,bool succeeded)414 PrinterJobHandler::HandleFailureStatusUpdateResponse(
415     const net::URLFetcher* source,
416     const GURL& url,
417     DictionaryValue* json_data,
418     bool succeeded) {
419   VLOG(1) << "CP_CONNECTOR: Handling failure status update response"
420           << ", printer id: " << printer_info_cloud_.printer_id;
421   base::MessageLoop::current()->PostTask(
422       FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
423   return CloudPrintURLFetcher::STOP_PROCESSING;
424 }
425 
Start()426 void PrinterJobHandler::Start() {
427   VLOG(1) << "CP_CONNECTOR: Starting printer job handler"
428           << ", printer id: " << printer_info_cloud_.printer_id
429           << ", task in progress: " << task_in_progress_;
430   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
431                             JOB_HANDLER_START, JOB_HANDLER_MAX);
432   if (task_in_progress_) {
433     // Multiple Starts can get posted because of multiple notifications
434     // We want to ignore the other ones that happen when a task is in progress.
435     return;
436   }
437   Reset();
438   if (!shutting_down_) {
439     // Check if we have work to do.
440     if (HavePendingTasks()) {
441       UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
442                                 JOB_HANDLER_PENDING_TASK, JOB_HANDLER_MAX);
443       if (!task_in_progress_ && printer_update_pending_) {
444         UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
445                                   JOB_HANDLER_PRINTER_UPDATE, JOB_HANDLER_MAX);
446         printer_update_pending_ = false;
447         task_in_progress_ = UpdatePrinterInfo();
448         VLOG(1) << "CP_CONNECTOR: Changed task in progress"
449                 << ", printer id: " << printer_info_cloud_.printer_id
450                 << ", task in progress: " << task_in_progress_;
451       }
452       if (!task_in_progress_ && job_check_pending_) {
453         UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
454                                   JOB_HANDLER_JOB_CHECK, JOB_HANDLER_MAX);
455         task_in_progress_ = true;
456         VLOG(1) << "CP_CONNECTOR: Changed task in progress"
457                 ", printer id: " << printer_info_cloud_.printer_id
458                 << ", task in progress: " << task_in_progress_;
459         job_check_pending_ = false;
460         // We need to fetch any pending jobs for this printer
461         SetNextJSONHandler(&PrinterJobHandler::HandleJobMetadataResponse);
462         request_ = CloudPrintURLFetcher::Create();
463         request_->StartGetRequest(
464             CloudPrintURLFetcher::REQUEST_JOB_FETCH,
465             GetUrlForJobFetch(
466                 cloud_print_server_url_, printer_info_cloud_.printer_id,
467                 job_fetch_reason_),
468             this,
469             kCloudPrintAPIMaxRetryCount,
470             std::string());
471         last_job_fetch_time_ = base::TimeTicks::Now();
472         VLOG(1) << "CP_CONNECTOR: Last job fetch time"
473                 << ", printer name: " << printer_info_.printer_name.c_str()
474                 << ", timestamp: " << last_job_fetch_time_.ToInternalValue();
475         job_fetch_reason_.clear();
476       }
477     }
478   }
479 }
480 
Stop()481 void PrinterJobHandler::Stop() {
482   VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
483           << ", printer id: " << printer_info_cloud_.printer_id;
484   task_in_progress_ = false;
485   VLOG(1) << "CP_CONNECTOR: Changed task in progress"
486           << ", printer id: " << printer_info_cloud_.printer_id
487           << ", task in progress: " << task_in_progress_;
488   Reset();
489   if (HavePendingTasks()) {
490     base::MessageLoop::current()->PostTask(
491         FROM_HERE, base::Bind(&PrinterJobHandler::Start, this));
492   }
493 }
494 
StartPrinting()495 void PrinterJobHandler::StartPrinting() {
496   VLOG(1) << "CP_CONNECTOR: Starting printing"
497           << ", printer id: " << printer_info_cloud_.printer_id;
498   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
499                             JOB_HANDLER_SET_START_PRINTING, JOB_HANDLER_MAX);
500   // We are done with the request object for now.
501   request_ = NULL;
502   if (!shutting_down_) {
503 #if defined(OS_WIN)
504     print_thread_.init_com_with_mta(true);
505 #endif
506     if (!print_thread_.Start()) {
507       VLOG(1) << "CP_CONNECTOR: Failed to start print thread"
508               << ", printer id: " << printer_info_cloud_.printer_id;
509       JobFailed(JOB_FAILED);
510     } else {
511       print_thread_.message_loop()->PostTask(
512           FROM_HERE, base::Bind(&PrinterJobHandler::DoPrint, this, job_details_,
513                                 printer_info_.printer_name));
514     }
515   }
516 }
517 
Reset()518 void PrinterJobHandler::Reset() {
519   job_details_.Clear();
520   request_ = NULL;
521   print_thread_.Stop();
522 }
523 
UpdateJobStatus(PrintJobStatus status,PrintJobError error)524 void PrinterJobHandler::UpdateJobStatus(PrintJobStatus status,
525                                         PrintJobError error) {
526   VLOG(1) << "CP_CONNECTOR: Updating job status"
527           << ", printer id: " << printer_info_cloud_.printer_id
528           << ", job id: " << job_details_.job_id_
529           << ", job status: " << status;
530   if (shutting_down_) {
531     VLOG(1) << "CP_CONNECTOR: Job status update aborted (shutting down)"
532             << ", printer id: " << printer_info_cloud_.printer_id
533             << ", job id: " << job_details_.job_id_;
534     return;
535   }
536   if (job_details_.job_id_.empty()) {
537     VLOG(1) << "CP_CONNECTOR: Job status update aborted (empty job id)"
538             << ", printer id: " << printer_info_cloud_.printer_id;
539     return;
540   }
541 
542   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobStatus", error, JOB_MAX);
543 
544   if (error == JOB_SUCCESS) {
545     DCHECK_EQ(status, PRINT_JOB_STATUS_IN_PROGRESS);
546     UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
547                               JOB_HANDLER_SET_IN_PROGRESS, JOB_HANDLER_MAX);
548     SetNextJSONHandler(
549         &PrinterJobHandler::HandleInProgressStatusUpdateResponse);
550   } else {
551     SetNextJSONHandler(
552         &PrinterJobHandler::HandleFailureStatusUpdateResponse);
553   }
554   request_ = CloudPrintURLFetcher::Create();
555   request_->StartGetRequest(
556       CloudPrintURLFetcher::REQUEST_UPDATE_JOB,
557       GetUrlForJobStatusUpdate(cloud_print_server_url_, job_details_.job_id_,
558                                status, error),
559       this, kCloudPrintAPIMaxRetryCount, std::string());
560 }
561 
RunScheduledJobCheck()562 void PrinterJobHandler::RunScheduledJobCheck() {
563   CheckForJobs(kJobFetchReasonRetry);
564 }
565 
SetNextJSONHandler(JSONDataHandler handler)566 void PrinterJobHandler::SetNextJSONHandler(JSONDataHandler handler) {
567   next_json_data_handler_ = handler;
568   next_data_handler_ = NULL;
569 }
570 
SetNextDataHandler(DataHandler handler)571 void PrinterJobHandler::SetNextDataHandler(DataHandler handler) {
572   next_data_handler_ = handler;
573   next_json_data_handler_ = NULL;
574 }
575 
JobFailed(PrintJobError error)576 void PrinterJobHandler::JobFailed(PrintJobError error) {
577   VLOG(1) << "CP_CONNECTOR: Job failed"
578           << ", printer id: " << printer_info_cloud_.printer_id
579           << ", job id: " << job_details_.job_id_
580           << ", error: " << error;
581   if (!shutting_down_) {
582     UpdateJobStatus(PRINT_JOB_STATUS_ERROR, error);
583     // This job failed, but others may be pending.  Schedule a check.
584     job_check_pending_ = true;
585     job_fetch_reason_ = kJobFetchReasonFailure;
586   }
587 }
588 
JobSpooled(PlatformJobId local_job_id)589 void PrinterJobHandler::JobSpooled(PlatformJobId local_job_id) {
590   VLOG(1) << "CP_CONNECTOR: Job spooled"
591           << ", printer id: " << printer_info_cloud_.printer_id
592           << ", job id: " << local_job_id;
593   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent", JOB_HANDLER_SPOOLED,
594                             JOB_HANDLER_MAX);
595   UMA_HISTOGRAM_LONG_TIMES("CloudPrint.SpoolingTime",
596                            base::Time::Now() - spooling_start_time_);
597   if (shutting_down_)
598     return;
599 
600   local_job_id_ = local_job_id;
601   print_thread_.Stop();
602 
603   // The print job has been spooled locally. We now need to create an object
604   // that monitors the status of the job and updates the server.
605   scoped_refptr<JobStatusUpdater> job_status_updater(
606       new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_,
607                             local_job_id_, cloud_print_server_url_,
608                             print_system_.get(), this));
609   job_status_updater_list_.push_back(job_status_updater);
610   base::MessageLoop::current()->PostTask(
611       FROM_HERE,
612       base::Bind(&JobStatusUpdater::UpdateStatus, job_status_updater.get()));
613 
614   CheckForJobs(kJobFetchReasonQueryMore);
615 
616   VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
617           << ", printer id: " << printer_info_cloud_.printer_id;
618   base::MessageLoop::current()->PostTask(
619       FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
620 }
621 
UpdatePrinterInfo()622 bool PrinterJobHandler::UpdatePrinterInfo() {
623   if (!printer_watcher_.get()) {
624     LOG(ERROR) << "CP_CONNECTOR: Printer watcher is missing."
625                << " Check printer server url for printer id: "
626                << printer_info_cloud_.printer_id;
627     return false;
628   }
629 
630   VLOG(1) << "CP_CONNECTOR: Updating printer info"
631           << ", printer id: " << printer_info_cloud_.printer_id;
632   // We need to update the parts of the printer info that have changed
633   // (could be printer name, description, status or capabilities).
634   // First asynchronously fetch the capabilities.
635   printing::PrinterBasicInfo printer_info;
636   printer_watcher_->GetCurrentPrinterInfo(&printer_info);
637 
638   // Asynchronously fetch the printer caps and defaults. The story will
639   // continue in OnReceivePrinterCaps.
640   print_system_->GetPrinterCapsAndDefaults(
641       printer_info.printer_name.c_str(),
642       base::Bind(&PrinterJobHandler::OnReceivePrinterCaps,
643                  weak_ptr_factory_.GetWeakPtr()));
644 
645   // While we are waiting for the data, pretend we have work to do and return
646   // true.
647   return true;
648 }
649 
HavePendingTasks()650 bool PrinterJobHandler::HavePendingTasks() {
651   return (job_check_pending_ || printer_update_pending_);
652 }
653 
ValidatePrintTicketFailed()654 void PrinterJobHandler::ValidatePrintTicketFailed() {
655   if (!shutting_down_) {
656     LOG(ERROR) << "CP_CONNECTOR: Failed validating print ticket"
657                << ", printer name: " << printer_info_.printer_name
658                << ", job id: " << job_details_.job_id_;
659     JobFailed(JOB_VALIDATE_TICKET_FAILED);
660   }
661 }
662 
OnReceivePrinterCaps(bool succeeded,const std::string & printer_name,const printing::PrinterCapsAndDefaults & caps_and_defaults)663 void PrinterJobHandler::OnReceivePrinterCaps(
664     bool succeeded,
665     const std::string& printer_name,
666     const printing::PrinterCapsAndDefaults& caps_and_defaults) {
667   printing::PrinterBasicInfo printer_info;
668   if (printer_watcher_.get())
669     printer_watcher_->GetCurrentPrinterInfo(&printer_info);
670 
671   std::string post_data;
672   std::string mime_boundary;
673   CreateMimeBoundaryForUpload(&mime_boundary);
674 
675   if (succeeded) {
676     std::string caps_hash =
677         base::MD5String(caps_and_defaults.printer_capabilities);
678     if (caps_hash != printer_info_cloud_.caps_hash) {
679       // Hashes don't match, we need to upload new capabilities (the defaults
680       // go for free along with the capabilities)
681       printer_info_cloud_.caps_hash = caps_hash;
682       net::AddMultipartValueForUpload(kPrinterCapsValue,
683           caps_and_defaults.printer_capabilities, mime_boundary,
684           caps_and_defaults.caps_mime_type, &post_data);
685       net::AddMultipartValueForUpload(kPrinterDefaultsValue,
686           caps_and_defaults.printer_defaults, mime_boundary,
687           caps_and_defaults.defaults_mime_type, &post_data);
688       net::AddMultipartValueForUpload(kPrinterCapsHashValue,
689           caps_hash, mime_boundary, std::string(), &post_data);
690     }
691   } else {
692     LOG(ERROR) << "Failed to get printer caps and defaults"
693                << ", printer name: " << printer_name;
694   }
695 
696   std::string tags_hash = GetHashOfPrinterInfo(printer_info);
697   if (tags_hash != printer_info_cloud_.tags_hash) {
698     printer_info_cloud_.tags_hash = tags_hash;
699     post_data += GetPostDataForPrinterInfo(printer_info, mime_boundary);
700     // Remove all the existing proxy tags.
701     std::string cp_tag_wildcard(kCloudPrintServiceProxyTagPrefix);
702     cp_tag_wildcard += ".*";
703     net::AddMultipartValueForUpload(kPrinterRemoveTagValue,
704         cp_tag_wildcard, mime_boundary, std::string(), &post_data);
705 
706     if (!last_caps_update_time_.is_null()) {
707       UMA_HISTOGRAM_CUSTOM_TIMES(
708           "CloudPrint.CapsUpdateInterval",
709           base::Time::Now() - last_caps_update_time_,
710           base::TimeDelta::FromMilliseconds(1),
711           base::TimeDelta::FromDays(7), 50);
712     }
713     last_caps_update_time_ = base::Time::Now();
714   }
715 
716   if (printer_info.printer_name != printer_info_.printer_name) {
717     net::AddMultipartValueForUpload(kPrinterNameValue,
718         printer_info.printer_name, mime_boundary, std::string(), &post_data);
719   }
720   if (printer_info.printer_description != printer_info_.printer_description) {
721     net::AddMultipartValueForUpload(kPrinterDescValue,
722       printer_info.printer_description, mime_boundary,
723       std::string(), &post_data);
724   }
725   if (printer_info.printer_status != printer_info_.printer_status) {
726     net::AddMultipartValueForUpload(kPrinterStatusValue,
727         base::StringPrintf("%d", printer_info.printer_status), mime_boundary,
728         std::string(), &post_data);
729   }
730 
731   // Add local_settings with a current XMPP ping interval.
732   if (printer_info_cloud_.pending_xmpp_timeout != 0) {
733     DCHECK(kMinXmppPingTimeoutSecs <= printer_info_cloud_.pending_xmpp_timeout);
734     net::AddMultipartValueForUpload(kPrinterLocalSettingsValue,
735         base::StringPrintf(kUpdateLocalSettingsXmppPingFormat,
736             printer_info_cloud_.current_xmpp_timeout),
737         mime_boundary, std::string(), &post_data);
738   }
739 
740   printer_info_ = printer_info;
741   if (!post_data.empty()) {
742     net::AddMultipartFinalDelimiterForUpload(mime_boundary, &post_data);
743     std::string mime_type("multipart/form-data; boundary=");
744     mime_type += mime_boundary;
745     SetNextJSONHandler(&PrinterJobHandler::HandlePrinterUpdateResponse);
746     request_ = CloudPrintURLFetcher::Create();
747     request_->StartPostRequest(
748         CloudPrintURLFetcher::REQUEST_UPDATE_PRINTER,
749         GetUrlForPrinterUpdate(
750             cloud_print_server_url_, printer_info_cloud_.printer_id),
751         this,
752         kCloudPrintAPIMaxRetryCount,
753         mime_type,
754         post_data,
755         std::string());
756   } else {
757     // We are done here. Go to the Stop state
758     VLOG(1) << "CP_CONNECTOR: Stopping printer job handler"
759             << ", printer name: " << printer_name;
760     base::MessageLoop::current()->PostTask(
761         FROM_HERE, base::Bind(&PrinterJobHandler::Stop, this));
762   }
763 }
764 
765 // The following methods are called on |print_thread_|. It is not safe to
766 // access any members other than |job_handler_message_loop_proxy_|,
767 // |job_spooler_| and |print_system_|.
DoPrint(const JobDetails & job_details,const std::string & printer_name)768 void PrinterJobHandler::DoPrint(const JobDetails& job_details,
769                                 const std::string& printer_name) {
770   job_spooler_ = print_system_->CreateJobSpooler();
771   UMA_HISTOGRAM_LONG_TIMES("CloudPrint.PrepareTime",
772                            base::Time::Now() - job_start_time_);
773   DCHECK(job_spooler_.get());
774   if (!job_spooler_.get())
775     return;
776   string16 document_name =
777       printing::SimplifyDocumentTitle(UTF8ToUTF16(job_details.job_title_));
778   if (document_name.empty()) {
779     document_name = printing::SimplifyDocumentTitle(
780         l10n_util::GetStringUTF16(IDS_DEFAULT_PRINT_DOCUMENT_TITLE));
781   }
782   UMA_HISTOGRAM_ENUMERATION("CloudPrint.JobHandlerEvent",
783                             JOB_HANDLER_START_SPOOLING, JOB_HANDLER_MAX);
784   spooling_start_time_ = base::Time::Now();
785   if (!job_spooler_->Spool(job_details.print_ticket_,
786                            job_details.print_data_file_path_,
787                            job_details.print_data_mime_type_,
788                            printer_name,
789                            UTF16ToUTF8(document_name),
790                            job_details.tags_,
791                            this)) {
792     OnJobSpoolFailed();
793   }
794 }
795 
796 }  // namespace cloud_print
797