Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(208)

Side by Side Diff: chrome/browser/printing/cloud_print/printer_job_handler.cc

Issue 1566047: First cut of Cloud Print Proxy implementation. The code is not enabled for no... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Final review changes Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/printing/cloud_print/printer_job_handler.h ('k') | chrome/browser/profile.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/printing/cloud_print/printer_job_handler.h"
6
7 #include "base/file_util.h"
8 #include "base/json/json_reader.h"
9 #include "base/md5.h"
10 #include "base/string_util.h"
11 #include "base/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "chrome/browser/printing/cloud_print/cloud_print_consts.h"
14 #include "chrome/browser/printing/cloud_print/cloud_print_helpers.h"
15 #include "chrome/browser/printing/cloud_print/job_status_updater.h"
16 #include "googleurl/src/gurl.h"
17 #include "net/http/http_response_headers.h"
18
19 PrinterJobHandler::PrinterJobHandler(
20 const cloud_print::PrinterBasicInfo& printer_info,
21 const std::string& printer_id,
22 const std::string& caps_hash,
23 const std::string& auth_token,
24 Delegate* delegate)
25 : printer_info_(printer_info),
26 printer_id_(printer_id),
27 auth_token_(auth_token),
28 last_caps_hash_(caps_hash),
29 delegate_(delegate),
30 local_job_id_(-1),
31 next_response_handler_(NULL),
32 server_error_count_(0),
33 print_thread_("Chrome_CloudPrintJobPrintThread"),
34 shutting_down_(false),
35 server_job_available_(false),
36 printer_update_pending_(true),
37 printer_delete_pending_(false),
38 task_in_progress_(false) {
39 }
40
41 bool PrinterJobHandler::Initialize() {
42 if (cloud_print::IsValidPrinter(printer_info_.printer_name)) {
43 printer_change_notifier_.StartWatching(printer_info_.printer_name, this);
44 NotifyJobAvailable();
45 } else {
46 // This printer does not exist any more. Delete it from the server.
47 OnPrinterDeleted();
48 }
49 return true;
50 }
51
52 PrinterJobHandler::~PrinterJobHandler() {
53 printer_change_notifier_.StopWatching();
54 }
55
56 void PrinterJobHandler::Reset() {
57 print_data_url_.clear();
58 job_details_.Clear();
59 request_.reset();
60 print_thread_.Stop();
61 }
62
63 void PrinterJobHandler::Start() {
64 if (task_in_progress_) {
65 // Multiple Starts can get posted because of multiple notifications
66 // We want to ignore the other ones that happen when a task is in progress.
67 return;
68 }
69 Reset();
70 if (!shutting_down_) {
71 // Check if we have work to do.
72 if (HavePendingTasks()) {
73 if (printer_delete_pending_) {
74 printer_delete_pending_ = false;
75 task_in_progress_ = true;
76 MakeServerRequest(
77 CloudPrintHelpers::GetUrlForPrinterDelete(printer_id_),
78 &PrinterJobHandler::HandlePrinterDeleteResponse);
79 }
80 if (!task_in_progress_ && printer_update_pending_) {
81 printer_update_pending_ = false;
82 task_in_progress_ = UpdatePrinterInfo();
83 }
84 if (!task_in_progress_ && server_job_available_) {
85 task_in_progress_ = true;
86 server_job_available_ = false;
87 // We need to fetch any pending jobs for this printer
88 MakeServerRequest(CloudPrintHelpers::GetUrlForJobFetch(printer_id_),
89 &PrinterJobHandler::HandleJobMetadataResponse);
90 }
91 }
92 }
93 }
94
95 void PrinterJobHandler::Stop() {
96 task_in_progress_ = false;
97 Reset();
98 if (HavePendingTasks()) {
99 MessageLoop::current()->PostTask(
100 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start));
101 }
102 }
103
104 void PrinterJobHandler::NotifyJobAvailable() {
105 server_job_available_ = true;
106 if (!task_in_progress_) {
107 MessageLoop::current()->PostTask(
108 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start));
109 }
110 }
111
112 bool PrinterJobHandler::UpdatePrinterInfo() {
113 // We need to update the parts of the printer info that have changed
114 // (could be printer name, description, status or capabilities).
115 cloud_print::PrinterBasicInfo printer_info;
116 printer_change_notifier_.GetCurrentPrinterInfo(&printer_info);
117 cloud_print::PrinterCapsAndDefaults printer_caps;
118 std::string post_data;
119 std::string mime_boundary;
120 if (cloud_print::GetPrinterCapsAndDefaults(printer_info.printer_name,
121 &printer_caps)) {
122 std::string caps_hash = MD5String(printer_caps.printer_capabilities);
123 CloudPrintHelpers::CreateMimeBoundaryForUpload(&mime_boundary);
124 if (caps_hash != last_caps_hash_) {
125 // Hashes don't match, we need to upload new capabilities (the defaults
126 // go for free along with the capabilities)
127 last_caps_hash_ = caps_hash;
128 CloudPrintHelpers::AddMultipartValueForUpload(
129 kPrinterCapsValue, printer_caps.printer_capabilities,
130 mime_boundary, printer_caps.caps_mime_type, &post_data);
131 CloudPrintHelpers::AddMultipartValueForUpload(
132 kPrinterDefaultsValue, printer_caps.printer_defaults,
133 mime_boundary, printer_caps.defaults_mime_type,
134 &post_data);
135 CloudPrintHelpers::AddMultipartValueForUpload(
136 WideToUTF8(kPrinterCapsHashValue).c_str(), caps_hash, mime_boundary,
137 std::string(), &post_data);
138 }
139 }
140 if (printer_info.printer_name != printer_info_.printer_name) {
141 CloudPrintHelpers::AddMultipartValueForUpload(kPrinterNameValue,
142 printer_info.printer_name,
143 mime_boundary,
144 std::string(), &post_data);
145 }
146 if (printer_info.printer_description != printer_info_.printer_description) {
147 CloudPrintHelpers::AddMultipartValueForUpload(
148 kPrinterDescValue, printer_info.printer_description, mime_boundary,
149 std::string() , &post_data);
150 }
151 if (printer_info.printer_status != printer_info_.printer_status) {
152 CloudPrintHelpers::AddMultipartValueForUpload(
153 kPrinterStatusValue, StringPrintf("%d", printer_info.printer_status),
154 mime_boundary, std::string(), &post_data);
155 }
156 printer_info_ = printer_info;
157 bool ret = false;
158 if (!post_data.empty()) {
159 // Terminate the request body
160 post_data.append("--" + mime_boundary + "--\r\n");
161 std::string mime_type("multipart/form-data; boundary=");
162 mime_type += mime_boundary;
163 request_.reset(
164 new URLFetcher(CloudPrintHelpers::GetUrlForPrinterUpdate(printer_id_),
165 URLFetcher::POST, this));
166 CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_);
167 request_->set_upload_data(mime_type, post_data);
168 next_response_handler_ = &PrinterJobHandler::HandlePrinterUpdateResponse;
169 request_->Start();
170 ret = true;
171 }
172 return ret;
173 }
174
175 // URLFetcher::Delegate implementation.
176 void PrinterJobHandler::OnURLFetchComplete(
177 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
178 int response_code, const ResponseCookies& cookies,
179 const std::string& data) {
180 if (!shutting_down_) {
181 DCHECK(source == request_.get());
182 // We need a next response handler because we are strictly a sequential
183 // state machine. We need each response handler to tell us which state to
184 // advance to next.
185 DCHECK(next_response_handler_);
186 if (!(this->*next_response_handler_)(source, url, status,
187 response_code, cookies, data)) {
188 // By contract, if the response handler returns false, it wants us to
189 // retry the request (upto the usual limit after which we give up and
190 // send the state machine to the Stop state);
191 HandleServerError(url);
192 }
193 }
194 }
195
196 // JobStatusUpdater::Delegate implementation
197 bool PrinterJobHandler::OnJobCompleted(JobStatusUpdater* updater) {
198 bool ret = false;
199 for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
200 index != job_status_updater_list_.end(); index++) {
201 if (index->get() == updater) {
202 job_status_updater_list_.erase(index);
203 ret = true;
204 break;
205 }
206 }
207 return ret;
208 }
209
210 // cloud_print::PrinterChangeNotifier::Delegate implementation
211 void PrinterJobHandler::OnPrinterAdded() {
212 // Should never get this notification for a printer
213 NOTREACHED();
214 }
215
216 void PrinterJobHandler::OnPrinterDeleted() {
217 printer_delete_pending_ = true;
218 if (!task_in_progress_) {
219 MessageLoop::current()->PostTask(
220 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start));
221 }
222 }
223
224 void PrinterJobHandler::OnPrinterChanged() {
225 printer_update_pending_ = true;
226 if (!task_in_progress_) {
227 MessageLoop::current()->PostTask(
228 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Start));
229 }
230 }
231
232 void PrinterJobHandler::OnJobChanged() {
233 // Some job on the printer changed. Loop through all our JobStatusUpdaters
234 // and have them check for updates.
235 for (JobStatusUpdaterList::iterator index = job_status_updater_list_.begin();
236 index != job_status_updater_list_.end(); index++) {
237 MessageLoop::current()->PostTask(
238 FROM_HERE, NewRunnableMethod(index->get(),
239 &JobStatusUpdater::UpdateStatus));
240 }
241 }
242
243 bool PrinterJobHandler::HandlePrinterUpdateResponse(
244 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
245 int response_code, const ResponseCookies& cookies,
246 const std::string& data) {
247 bool ret = false;
248 // If there was a network error or a non-200 response (which, for our purposes
249 // is the same as a network error), we want to retry.
250 if (status.is_success() && (response_code == 200)) {
251 bool succeeded = false;
252 DictionaryValue* response_dict = NULL;
253 CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict);
254 // If we get valid JSON back, we are done.
255 if (NULL != response_dict) {
256 ret = true;
257 }
258 }
259 if (ret) {
260 // We are done here. Go to the Stop state
261 MessageLoop::current()->PostTask(
262 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
263 } else {
264 // Since we failed to update the server, set the flag again.
265 printer_update_pending_ = true;
266 }
267 return ret;
268 }
269
270 bool PrinterJobHandler::HandlePrinterDeleteResponse(
271 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
272 int response_code, const ResponseCookies& cookies,
273 const std::string& data) {
274 bool ret = false;
275 // If there was a network error or a non-200 response (which, for our purposes
276 // is the same as a network error), we want to retry.
277 if (status.is_success() && (response_code == 200)) {
278 bool succeeded = false;
279 DictionaryValue* response_dict = NULL;
280 CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict);
281 // If we get valid JSON back, we are done.
282 if (NULL != response_dict) {
283 ret = true;
284 }
285 }
286 if (ret) {
287 // The printer has been deleted. Shutdown the handler class.
288 MessageLoop::current()->PostTask(
289 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Shutdown));
290 } else {
291 // Since we failed to update the server, set the flag again.
292 printer_delete_pending_ = true;
293 }
294 return ret;
295 }
296
297 bool PrinterJobHandler::HandleJobMetadataResponse(
298 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
299 int response_code, const ResponseCookies& cookies,
300 const std::string& data) {
301 // If there was a network error or a non-200 response (which, for our purposes
302 // is the same as a network error), we want to retry.
303 if (!status.is_success() || (response_code != 200)) {
304 return false;
305 }
306 bool succeeded = false;
307 DictionaryValue* response_dict = NULL;
308 CloudPrintHelpers::ParseResponseJSON(data, &succeeded, &response_dict);
309 if (NULL == response_dict) {
310 // If we did not get a valid JSON response, we need to retry.
311 return false;
312 }
313 Task* next_task = NULL;
314 if (succeeded) {
315 ListValue* job_list = NULL;
316 response_dict->GetList(kJobListValue, &job_list);
317 if (job_list) {
318 // Even though it is a job list, for now we are only interested in the
319 // first job
320 DictionaryValue* job_data = NULL;
321 if (job_list->GetDictionary(0, &job_data)) {
322 job_data->GetString(kIdValue, &job_details_.job_id_);
323 job_data->GetString(kTitleValue, &job_details_.job_title_);
324 std::string print_ticket_url;
325 job_data->GetString(kTicketUrlValue, &print_ticket_url);
326 job_data->GetString(kFileUrlValue, &print_data_url_);
327 next_task = NewRunnableMethod(
328 this, &PrinterJobHandler::MakeServerRequest,
329 GURL(print_ticket_url.c_str()),
330 &PrinterJobHandler::HandlePrintTicketResponse);
331 }
332 }
333 }
334 if (!next_task) {
335 // If we got a valid JSON but there were no jobs, we are done
336 next_task = NewRunnableMethod(this, &PrinterJobHandler::Stop);
337 }
338 delete response_dict;
339 DCHECK(next_task);
340 MessageLoop::current()->PostTask(FROM_HERE, next_task);
341 return true;
342 }
343
344 bool PrinterJobHandler::HandlePrintTicketResponse(
345 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
346 int response_code, const ResponseCookies& cookies,
347 const std::string& data) {
348 // If there was a network error or a non-200 response (which, for our purposes
349 // is the same as a network error), we want to retry.
350 if (!status.is_success() || (response_code != 200)) {
351 return false;
352 }
353 if (cloud_print::ValidatePrintTicket(printer_info_.printer_name, data)) {
354 job_details_.print_ticket_ = data;
355 MessageLoop::current()->PostTask(
356 FROM_HERE,
357 NewRunnableMethod(this,
358 &PrinterJobHandler::MakeServerRequest,
359 GURL(print_data_url_.c_str()),
360 &PrinterJobHandler::HandlePrintDataResponse));
361 } else {
362 // The print ticket was not valid. We are done here.
363 MessageLoop::current()->PostTask(
364 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::JobFailed,
365 INVALID_JOB_DATA));
366 }
367 return true;
368 }
369
370 bool PrinterJobHandler::HandlePrintDataResponse(const URLFetcher* source,
371 const GURL& url,
372 const URLRequestStatus& status,
373 int response_code,
374 const ResponseCookies& cookies,
375 const std::string& data) {
376 // If there was a network error or a non-200 response (which, for our purposes
377 // is the same as a network error), we want to retry.
378 if (!status.is_success() || (response_code != 200)) {
379 return false;
380 }
381 Task* next_task = NULL;
382 if (file_util::CreateTemporaryFile(&job_details_.print_data_file_path_)) {
383 int ret = file_util::WriteFile(job_details_.print_data_file_path_,
384 data.c_str(),
385 data.length());
386 source->response_headers()->GetMimeType(
387 &job_details_.print_data_mime_type_);
388 DCHECK(ret == static_cast<int>(data.length()));
389 if (ret == static_cast<int>(data.length())) {
390 next_task = NewRunnableMethod(this, &PrinterJobHandler::StartPrinting);
391 }
392 }
393 // If there was no task allocated above, then there was an error in
394 // saving the print data, bail out here.
395 if (!next_task) {
396 next_task = NewRunnableMethod(this, &PrinterJobHandler::JobFailed,
397 JOB_DOWNLOAD_FAILED);
398 }
399 MessageLoop::current()->PostTask(FROM_HERE, next_task);
400 return true;
401 }
402
403 void PrinterJobHandler::StartPrinting() {
404 // We are done with the request object for now.
405 request_.reset();
406 if (!shutting_down_) {
407 if (!print_thread_.Start()) {
408 JobFailed(PRINT_FAILED);
409 } else {
410 print_thread_.message_loop()->PostTask(
411 FROM_HERE, NewRunnableFunction(&PrinterJobHandler::DoPrint,
412 job_details_,
413 printer_info_.printer_name, this,
414 MessageLoop::current()));
415 }
416 }
417 }
418
419 void PrinterJobHandler::JobFailed(PrintJobError error) {
420 if (!shutting_down_) {
421 UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_ERROR, error);
422 }
423 }
424
425 void PrinterJobHandler::JobSpooled(cloud_print::PlatformJobId local_job_id) {
426 if (!shutting_down_) {
427 local_job_id_ = local_job_id;
428 UpdateJobStatus(cloud_print::PRINT_JOB_STATUS_IN_PROGRESS, SUCCESS);
429 print_thread_.Stop();
430 }
431 }
432
433 void PrinterJobHandler::Shutdown() {
434 Reset();
435 shutting_down_ = true;
436 while (!job_status_updater_list_.empty()) {
437 // Calling Stop() will cause the OnJobCompleted to be called which will
438 // remove the updater object from the list.
439 job_status_updater_list_.front()->Stop();
440 }
441 if (delegate_) {
442 delegate_->OnPrinterJobHandlerShutdown(this, printer_id_);
443 }
444 }
445
446 void PrinterJobHandler::HandleServerError(const GURL& url) {
447 Task* task_to_retry = NewRunnableMethod(this,
448 &PrinterJobHandler::MakeServerRequest,
449 url, next_response_handler_);
450 Task* task_on_give_up = NewRunnableMethod(this, &PrinterJobHandler::Stop);
451 CloudPrintHelpers::HandleServerError(&server_error_count_, kMaxRetryCount,
452 -1, kBaseRetryInterval, task_to_retry,
453 task_on_give_up);
454 }
455
456 void PrinterJobHandler::UpdateJobStatus(cloud_print::PrintJobStatus status,
457 PrintJobError error) {
458 if (!shutting_down_) {
459 if (!job_details_.job_id_.empty()) {
460 ResponseHandler response_handler = NULL;
461 if (error == SUCCESS) {
462 response_handler =
463 &PrinterJobHandler::HandleSuccessStatusUpdateResponse;
464 } else {
465 response_handler =
466 &PrinterJobHandler::HandleFailureStatusUpdateResponse;
467 }
468 MakeServerRequest(
469 CloudPrintHelpers::GetUrlForJobStatusUpdate(job_details_.job_id_,
470 status),
471 response_handler);
472 }
473 }
474 }
475
476 bool PrinterJobHandler::HandleSuccessStatusUpdateResponse(
477 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
478 int response_code, const ResponseCookies& cookies,
479 const std::string& data) {
480 // If there was a network error or a non-200 response (which, for our purposes
481 // is the same as a network error), we want to retry.
482 if (!status.is_success() || (response_code != 200)) {
483 return false;
484 }
485 // The print job has been spooled locally. We now need to create an object
486 // that monitors the status of the job and updates the server.
487 scoped_refptr<JobStatusUpdater> job_status_updater =
488 new JobStatusUpdater(printer_info_.printer_name, job_details_.job_id_,
489 local_job_id_, auth_token_, this);
490 job_status_updater_list_.push_back(job_status_updater);
491 MessageLoop::current()->PostTask(
492 FROM_HERE, NewRunnableMethod(job_status_updater.get(),
493 &JobStatusUpdater::UpdateStatus));
494 bool succeeded = false;
495 CloudPrintHelpers::ParseResponseJSON(data, &succeeded, NULL);
496 if (succeeded) {
497 // Since we just printed successfully, we want to look for more jobs.
498 server_job_available_ = true;
499 }
500 MessageLoop::current()->PostTask(
501 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
502 return true;
503 }
504
505 bool PrinterJobHandler::HandleFailureStatusUpdateResponse(
506 const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
507 int response_code, const ResponseCookies& cookies,
508 const std::string& data) {
509 // If there was a network error or a non-200 response (which, for our purposes
510 // is the same as a network error), we want to retry.
511 if (!status.is_success() || (response_code != 200)) {
512 return false;
513 }
514 MessageLoop::current()->PostTask(
515 FROM_HERE, NewRunnableMethod(this, &PrinterJobHandler::Stop));
516 return true;
517 }
518
519 void PrinterJobHandler::MakeServerRequest(const GURL& url,
520 ResponseHandler response_handler) {
521 if (!shutting_down_) {
522 request_.reset(new URLFetcher(url, URLFetcher::GET, this));
523 server_error_count_ = 0;
524 CloudPrintHelpers::PrepCloudPrintRequest(request_.get(), auth_token_);
525 // Set up the next response handler
526 next_response_handler_ = response_handler;
527 request_->Start();
528 }
529 }
530
531 bool PrinterJobHandler::HavePendingTasks() {
532 return server_job_available_ || printer_update_pending_ ||
533 printer_delete_pending_;
534 }
535
536
537 void PrinterJobHandler::DoPrint(const JobDetails& job_details,
538 const std::string& printer_name,
539 PrinterJobHandler* job_handler,
540 MessageLoop* job_message_loop) {
541 DCHECK(job_handler);
542 DCHECK(job_message_loop);
543 cloud_print::PlatformJobId job_id = -1;
544 if (cloud_print::SpoolPrintJob(job_details.print_ticket_,
545 job_details.print_data_file_path_,
546 job_details.print_data_mime_type_,
547 printer_name,
548 job_details.job_title_, &job_id)) {
549 job_message_loop->PostTask(FROM_HERE,
550 NewRunnableMethod(job_handler,
551 &PrinterJobHandler::JobSpooled,
552 job_id));
553 } else {
554 job_message_loop->PostTask(FROM_HERE,
555 NewRunnableMethod(job_handler,
556 &PrinterJobHandler::JobFailed,
557 PRINT_FAILED));
558 }
559 }
560
OLDNEW
« no previous file with comments | « chrome/browser/printing/cloud_print/printer_job_handler.h ('k') | chrome/browser/profile.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698