1 # -*- coding: utf-8 -*-
3 # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 from __future__ import division
29 import urllib # TODO: Replace with urllib2 (urllib is deprecated in Python 3.0)
34 from base.codes import *
35 from base import device, utils, codes, dime
38 #import xml.parsers.expat as expat
41 # **************************************************************************** #
43 http_result_pat = re.compile("""HTTP/\d.\d\s(\d+)""", re.I)
49 DATE_FORMAT_MM_DD_YYYY = 1
50 DATE_FORMAT_DD_MM_YYYY = 2
51 DATE_FORMAT_YYYY_MM_DD = 3
59 PIXELS_PER_LINE = 2528
62 # **************************************************************************** #
63 class SOAPFaxDevice(FaxDevice):
65 def __init__(self, device_uri=None, printer_name=None,
67 fax_type=FAX_TYPE_NONE,
70 FaxDevice.__init__(self, device_uri,
75 self.send_fax_thread = None
76 self.upload_log_thread = None
79 self.http_host = self.host
81 self.http_host = 'localhost'
84 def post(self, url, post):
86 for k, v in post.items():
87 s.append("%s=%s" % (k, urllib.quote(str(v))))
93 data = """POST %s HTTP/1.1
94 Connection: Keep-alive
98 Cache-control: No-cache
100 %s""" % (url, self.http_host, len(s), s)
104 ret = cStringIO.StringIO()
106 while self.readEWS(4096, ret, timeout=5):
115 match = http_result_pat.match(ret)
118 code = int(match.group(1))
119 except (ValueError, TypeError):
122 return code == HTTP_OK
125 def setPhoneNum(self, num):
126 return self.post("/hp/device/set_config.html", {"FaxNumber": str(num)})
129 def getPhoneNum(self):
130 stream = cStringIO.StringIO()
131 self.getEWSUrl("/hp/device/settings_fax_setup_wizard.xml", stream)
132 fax_setup = utils.XMLToDictParser().parseXML(stream.getvalue())
133 return fax_setup['faxsetupwizard-faxvoicenumber-faxnumber']
135 phone_num = property(getPhoneNum, setPhoneNum)
138 def setStationName(self, name):
139 return self.post("/hp/device/set_config.html", {"FaxCompanyName": str(name)})
142 def getStationName(self):
143 stream = cStringIO.StringIO()
144 self.getEWSUrl("/hp/device/settings_fax_setup_wizard.xml", stream)
145 fax_setup = utils.XMLToDictParser().parseXML(stream.getvalue())
146 return fax_setup['faxsetupwizard-userinformation-faxcompanyname']
148 station_name = property(getStationName, setStationName)
151 def setDateAndTime(self):
152 stream = cStringIO.StringIO()
153 self.getEWSUrl("/hp/device/settings_fax_setup_wizard.xml", stream)
154 fax_setup = utils.XMLToDictParser().parseXML(stream.getvalue())
155 timeformat = fax_setup['faxsetupwizard-time-timeformat']
158 timeformat = int(timeformat)
159 except (ValueError, TypeError):
160 timeformat = TIME_FORMAT_AM_PM
162 log.debug("timeformat: %d" % timeformat)
164 dateformat = fax_setup['faxsetupwizard-date-dateformat']
167 dateformat = int(dateformat)
168 except (ValueError, TypeError):
169 dateformat = DATE_FORMAT_DD_MM_YYYY
171 log.debug("dateformat: %d" % dateformat)
180 if timeformat == TIME_FORMAT_AM_PM and hr > 12:
183 post = {"DateFormat" : dateformat,
187 "TimeFormat" : timeformat,
191 if timeformat == TIME_FORMAT_AM_PM:
194 return self.post("/hp/device/set_config.html", post)
197 def sendFaxes(self, phone_num_list, fax_file_list, cover_message='', cover_re='',
198 cover_func=None, preserve_formatting=False, printer_name='',
199 update_queue=None, event_queue=None):
201 if not self.isSendFaxActive():
203 self.send_fax_thread = SOAPFaxSendThread(self, self.service, phone_num_list, fax_file_list,
204 cover_message, cover_re, cover_func,
206 printer_name, update_queue,
209 self.send_fax_thread.start()
215 # **************************************************************************** #
216 class SOAPFaxSendThread(FaxSendThread):
217 def __init__(self, dev, service, phone_num_list, fax_file_list,
218 cover_message='', cover_re='', cover_func=None, preserve_formatting=False,
219 printer_name='', update_queue=None, event_queue=None):
221 FaxSendThread.__init__(self, dev, service, phone_num_list, fax_file_list,
222 cover_message, cover_re, cover_func, preserve_formatting,
223 printer_name, update_queue, event_queue)
225 self.job_id = utils.gen_random_uuid()
226 log.debug("JobId: %s" % self.job_id)
229 self.http_host = "%s:8295" % self.dev.host
231 self.http_host = 'localhost:8295'
233 #self.http_host = 'localhost'
237 #results = {} # {'file' : error_code,...}
243 STATE_READ_SENDER_INFO = 30
245 STATE_COUNT_PAGES = 50
246 STATE_NEXT_RECIPIENT = 60
247 STATE_COVER_PAGE = 70
248 STATE_SINGLE_FILE = 80
249 STATE_MERGE_FILES = 90
250 STATE_SINGLE_FILE = 100
255 next_recipient = self.next_recipient_gen()
257 state = STATE_READ_SENDER_INFO
258 self.rendered_file_list = []
260 while state != STATE_DONE: # --------------------------------- Fax state machine
261 if self.check_for_cancel():
262 state = STATE_ABORTED
264 log.debug("STATE=(%d, 0, 0)" % state)
266 if state == STATE_ABORTED: # --------------------------------- Aborted (10, 0, 0)
267 log.error("Aborted by user.")
268 self.write_queue((STATUS_IDLE, 0, ''))
269 state = STATE_CLEANUP
272 elif state == STATE_SUCCESS: # --------------------------------- Success (20, 0, 0)
273 log.debug("Success.")
274 self.write_queue((STATUS_COMPLETED, 0, ''))
275 state = STATE_CLEANUP
278 elif state == STATE_ERROR: # --------------------------------- Error (130, 0, 0)
279 log.error("Error, aborting.")
280 self.write_queue((STATUS_ERROR, 0, ''))
281 state = STATE_CLEANUP
284 elif state == STATE_BUSY: # --------------------------------- Busy (25, 0, 0)
285 log.error("Device busy, aborting.")
286 self.write_queue((STATUS_BUSY, 0, ''))
287 state = STATE_CLEANUP
290 elif state == STATE_READ_SENDER_INFO: # --------------------------------- Get sender info (30, 0, 0)
291 log.debug("%s State: Get sender info" % ("*"*20))
292 state = STATE_PRERENDER
297 log.error("Unable to open device (%s)." % e.msg)
301 self.sender_name = self.dev.station_name
302 log.debug("Sender name=%s" % self.sender_name)
303 self.sender_fax = self.dev.phone_num
304 log.debug("Sender fax=%s" % self.sender_fax)
306 log.error("HTTP GET failed!")
313 elif state == STATE_PRERENDER: # --------------------------------- Pre-render non-G4 files (40, 0, 0)
314 log.debug("%s State: Pre-render non-G4 files" % ("*"*20))
315 state = self.pre_render(STATE_COUNT_PAGES)
317 elif state == STATE_COUNT_PAGES: # --------------------------------- Get total page count (50, 0, 0)
318 log.debug("%s State: Get total page count" % ("*"*20))
319 state = self.count_pages(STATE_NEXT_RECIPIENT)
321 elif state == STATE_NEXT_RECIPIENT: # --------------------------------- Loop for multiple recipients (60, 0, 0)
322 log.debug("%s State: Next recipient" % ("*"*20))
323 state = STATE_COVER_PAGE
326 recipient = next_recipient.next()
327 log.debug("Processing for recipient %s" % recipient['name'])
328 self.write_queue((STATUS_SENDING_TO_RECIPIENT, 0, recipient['name']))
329 except StopIteration:
330 state = STATE_SUCCESS
331 log.debug("Last recipient.")
334 recipient_file_list = self.rendered_file_list[:]
337 elif state == STATE_COVER_PAGE: # --------------------------------- Create cover page (70, 0, 0)
338 log.debug("%s State: Render cover page" % ("*"*20))
339 state = self.cover_page(recipient)
342 elif state == STATE_SINGLE_FILE: # --------------------------------- Special case for single file (no merge) (80, 0, 0)
343 log.debug("%s State: Handle single file" % ("*"*20))
344 state = self.single_file(STATE_SEND_FAX)
346 elif state == STATE_MERGE_FILES: # --------------------------------- Merge multiple G4 files (90, 0, 0)
347 log.debug("%s State: Merge multiple files" % ("*"*20))
348 state = self.merge_files(STATE_SEND_FAX)
350 elif state == STATE_SEND_FAX: # --------------------------------- Send fax state machine (110, 0, 0)
351 log.debug("%s State: Send fax" % ("*"*20))
352 state = STATE_NEXT_RECIPIENT
354 FAX_SEND_STATE_DONE = 0
355 FAX_SEND_STATE_ABORT = 10
356 FAX_SEND_STATE_ERROR = 20
357 FAX_SEND_STATE_BUSY = 25
358 FAX_SEND_STATE_SUCCESS = 30
359 FAX_SEND_STATE_DEVICE_OPEN = 40
360 FAX_SEND_STATE_BEGINJOB = 50
361 FAX_SEND_STATE_DOWNLOADPAGES = 60
362 FAX_SEND_STATE_ENDJOB = 70
363 FAX_SEND_STATE_CANCELJOB = 80
364 FAX_SEND_STATE_CLOSE_SESSION = 170
366 monitor_state = False
367 fax_send_state = FAX_SEND_STATE_DEVICE_OPEN
369 while fax_send_state != FAX_SEND_STATE_DONE:
371 if self.check_for_cancel():
372 log.error("Fax send aborted.")
373 fax_send_state = FAX_SEND_STATE_ABORT
376 fax_state = self.getFaxDownloadState()
377 if not fax_state in (pml.UPDN_STATE_XFERACTIVE, pml.UPDN_STATE_XFERDONE):
378 log.error("D/L error state=%d" % fax_state)
379 fax_send_state = FAX_SEND_STATE_ERROR
382 log.debug("STATE=(%d, %d, 0)" % (STATE_SEND_FAX, fax_send_state))
384 if fax_send_state == FAX_SEND_STATE_ABORT: # -------------- Abort (110, 10, 0)
385 monitor_state = False
386 fax_send_state = FAX_SEND_STATE_CANCELJOB
387 state = STATE_ABORTED
389 elif fax_send_state == FAX_SEND_STATE_ERROR: # -------------- Error (110, 20, 0)
390 log.error("Fax send error.")
391 monitor_state = False
392 fax_send_state = FAX_SEND_STATE_CLOSE_SESSION
395 elif fax_send_state == FAX_SEND_STATE_BUSY: # -------------- Busy (110, 25, 0)
396 log.error("Fax device busy.")
397 monitor_state = False
398 fax_send_state = FAX_SEND_STATE_CLOSE_SESSION
401 elif fax_send_state == FAX_SEND_STATE_SUCCESS: # -------------- Success (110, 30, 0)
402 log.debug("Fax send success.")
403 monitor_state = False
404 fax_send_state = FAX_SEND_STATE_CLOSE_SESSION
405 state = STATE_NEXT_RECIPIENT
407 elif fax_send_state == FAX_SEND_STATE_DEVICE_OPEN: # -------------- Device open (110, 40, 0)
408 log.debug("%s State: Open device" % ("*"*20))
409 fax_send_state = FAX_SEND_STATE_BEGINJOB
413 log.error("Unable to open device (%s)." % e.msg)
414 fax_send_state = FAX_SEND_STATE_ERROR
416 if self.dev.device_state == DEVICE_STATE_NOT_FOUND:
417 fax_send_state = FAX_SEND_STATE_ERROR
419 elif fax_send_state == FAX_SEND_STATE_BEGINJOB: # -------------- BeginJob (110, 50, 0)
420 log.debug("%s State: BeginJob" % ("*"*20))
423 ff = file(self.f, 'r')
425 log.error("Unable to read fax file.")
426 fax_send_state = FAX_SEND_STATE_ERROR
430 header = ff.read(FILE_HEADER_SIZE)
432 log.error("Unable to read fax file.")
433 fax_send_state = FAX_SEND_STATE_ERROR
436 magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
437 resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)
439 if magic != 'hplip_g3':
440 log.error("Invalid file header. Bad magic.")
441 fax_send_state = FAX_SEND_STATE_ERROR
443 log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
444 (magic, version, total_pages, hort_dpi, vert_dpi, page_size,
445 resolution, encoding))
449 faxnum = recipient['fax'].encode('ascii')
452 if resolution == RESOLUTION_STD:
454 elif resolution == RESOLUTION_FINE:
456 elif resolution == RESOLUTION_300DPI:
460 """<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><Fax:BeginJob xmlns:Fax="urn:Fax"><ticket xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Fax:Ticket"><jobId xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">$job_id</jobId><resolution xsi:type="Fax:Resolution">$res</resolution><delay xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:positiveInteger">$delay</delay><phoneNumber xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">$faxnum</phoneNumber><speedDial xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:positiveInteger">$speeddial</speedDial></ticket></Fax:BeginJob></SOAP-ENV:Body></SOAP-ENV:Envelope>""")
462 data = self.format_http(soap)
466 file('beginjob.log', 'w').write(data)
468 self.dev.openSoapFax()
469 self.dev.writeSoapFax(data)
470 ret = cStringIO.StringIO()
472 while self.dev.readSoapFax(8192, ret, timeout=5):
478 file('beginjob_ret.log', 'w').write(ret)
481 self.dev.closeSoapFax()
483 if self.get_error_code(ret) == HTTP_OK:
484 fax_send_state = FAX_SEND_STATE_DOWNLOADPAGES
486 fax_send_state = FAX_SEND_STATE_ERROR
489 elif fax_send_state == FAX_SEND_STATE_DOWNLOADPAGES: # -------------- DownloadPages (110, 60, 0)
490 log.debug("%s State: DownloadPages" % ("*"*20))
492 for p in range(total_pages):
494 if self.check_for_cancel():
495 fax_send_state = FAX_SEND_STATE_ABORT
497 if fax_send_state == FAX_SEND_STATE_ABORT:
501 header = ff.read(PAGE_HEADER_SIZE)
503 log.error("Unable to read fax file.")
504 fax_send_state = FAX_SEND_STATE_ERROR
507 page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, reserved2 = \
508 self.decode_page_header(header)
510 log.debug("Page=%d PPR=%d RPP=%d BPP=%d Thumb=%d" %
511 (page_num, ppr, rpp, bytes_to_read, thumbnail_bytes))
513 if ppr != PIXELS_PER_LINE:
514 log.error("Pixels per line (width) must be %d!" % PIXELS_PER_LINE)
516 page.write(ff.read(bytes_to_read))
517 thumbnail = ff.read(thumbnail_bytes) # thrown away for now (should be 0 read)
521 data = page.read(bytes_to_read)
523 log.error("Unable to read fax file.")
524 fax_send_state = FAX_SEND_STATE_ERROR
528 log.error("No data!")
529 fax_send_state = FAX_SEND_STATE_ERROR
536 """<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header><jobId xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string" SOAP-ENV:mustUnderstand="1">$job_id</jobId></SOAP-ENV:Header><SOAP-ENV:Body><Fax:DownloadPage xmlns:Fax="urn:Fax"><height xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:positiveInteger">$height</height></Fax:DownloadPage></SOAP-ENV:Body></SOAP-ENV:Envelope>""")
539 m.add_record(dime.Record("cid:id0", "http://schemas.xmlsoap.org/soap/envelope/",
540 dime.TYPE_T_URI, soap))
542 m.add_record(dime.Record("", "image/g4fax", dime.TYPE_T_MIME, data))
544 output = cStringIO.StringIO()
546 data = self.format_http(output.getvalue(), content_type="application/dime")
549 file('downloadpages%d.log' % p, 'w').write(data)
552 self.dev.writeSoapFax(data)
554 fax_send_state = FAX_SEND_STATE_ERROR
556 ret = cStringIO.StringIO()
559 while self.dev.readSoapFax(8192, ret, timeout=5):
562 fax_send_state = FAX_SEND_STATE_ERROR
567 file('downloadpages%d_ret.log' % p, 'w').write(ret)
570 self.dev.closeSoapFax()
572 if self.get_error_code(ret) != HTTP_OK:
573 fax_send_state = FAX_SEND_STATE_ERROR
580 fax_send_state = FAX_SEND_STATE_ENDJOB
583 elif fax_send_state == FAX_SEND_STATE_ENDJOB: # -------------- EndJob (110, 70, 0)
584 log.debug("%s State: EndJob" % ("*"*20))
589 """<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header><jobId xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string" SOAP-ENV:mustUnderstand="1">$job_id</jobId></SOAP-ENV:Header><SOAP-ENV:Body><Fax:EndJob xmlns:Fax="urn:Fax"><jobId xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">$job_id</jobId></Fax:EndJob></SOAP-ENV:Body></SOAP-ENV:Envelope>""")
591 data = self.format_http(soap)
596 file('endjob.log', 'w').write(data)
598 self.dev.writeSoapFax(data)
599 ret = cStringIO.StringIO()
601 while self.dev.readSoapFax(8192, ret, timeout=5):
607 file('endjob_ret.log', 'w').write(ret)
610 self.dev.closeSoapFax()
612 if self.get_error_code(ret) == HTTP_OK:
613 fax_send_state = FAX_SEND_STATE_SUCCESS
615 fax_send_state = FAX_SEND_STATE_ERROR
617 elif fax_send_state == FAX_SEND_STATE_CANCELJOB: # -------------- CancelJob (110, 80, 0)
618 log.debug("%s State: CancelJob" % ("*"*20))
623 """<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header><jobId xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string" SOAP-ENV:mustUnderstand="1">$job_id</jobId></SOAP-ENV:Header><SOAP-ENV:Body><Fax:CancelJob xmlns:Fax="urn:Fax"><jobId xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">$job_id</jobId></Fax:CancelJob></SOAP-ENV:Body></SOAP-ENV:Envelope>""")
625 data = self.format_http(soap)
630 file('canceljob.log', 'w').write(data)
632 self.dev.writeSoapFax(data)
633 ret = cStringIO.StringIO()
635 while self.dev.readSoapFax(8192, ret, timeout=5):
641 file('canceljob_ret.log', 'w').write(ret)
644 self.dev.closeSoapFax()
646 if self.get_error_code(ret) == HTTP_OK:
647 fax_send_state = FAX_SEND_STATE_CLOSE_SESSION
649 fax_send_state = FAX_SEND_STATE_ERROR
652 elif fax_send_state == FAX_SEND_STATE_CLOSE_SESSION: # -------------- Close session (110, 170, 0)
653 log.debug("%s State: Close session" % ("*"*20))
654 log.debug("Closing session...")
668 self.dev.closeSoapFax()
671 fax_send_state = FAX_SEND_STATE_DONE # Exit inner state machine
674 elif state == STATE_CLEANUP: # --------------------------------- Cleanup (120, 0, 0)
675 log.debug("%s State: Cleanup" % ("*"*20))
677 if self.remove_temp_file:
678 log.debug("Removing merged file: %s" % self.f)
683 log.debug("Not found")
685 state = STATE_DONE # Exit outer state machine
688 def get_error_code(self, ret):
689 if not ret: return HTTP_ERROR
691 match = http_result_pat.match(ret)
693 if match is None: return HTTP_OK
695 code = int(match.group(1))
696 except (ValueError, TypeError):
702 def format_http(self, soap, content_type="text/xml; charset=utf-8"):
703 host = self.http_host
709 User-Agent: hplip/2.0\r
710 Content-Type: $content_type\r
711 Content-Length: $soap_len\r