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

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

Powered by Google App Engine
This is Rietveld 408576698