# -*- coding: utf-8 -*- # # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Author: Don Welch # from __future__ import division # Std Lib import sys import os import time import cStringIO import urllib # TODO: Replace with urllib2 (urllib is deprecated in Python 3.0) import re # Local from base.g import * from base.codes import * from base import device, utils, codes, dime from fax import * #import xml.parsers.expat as expat # **************************************************************************** # http_result_pat = re.compile("""HTTP/\d.\d\s(\d+)""", re.I) TIME_FORMAT_AM_PM = 1 TIME_FORMAT_24HR = 2 DATE_FORMAT_MM_DD_YYYY = 1 DATE_FORMAT_DD_MM_YYYY = 2 DATE_FORMAT_YYYY_MM_DD = 3 AM = 1 PM = 0 HTTP_OK = 200 HTTP_ERROR = 500 PIXELS_PER_LINE = 2528 # **************************************************************************** # class SOAPFaxDevice(FaxDevice): def __init__(self, device_uri=None, printer_name=None, callback=None, fax_type=FAX_TYPE_NONE, disable_dbus=False): FaxDevice.__init__(self, device_uri, printer_name, callback, fax_type, disable_dbus) self.send_fax_thread = None self.upload_log_thread = None if self.bus == 'net': self.http_host = self.host else: self.http_host = 'localhost' def post(self, url, post): s = [] for k, v in post.items(): s.append("%s=%s" % (k, urllib.quote(str(v)))) s = '&'.join(s) log.debug(s) data = """POST %s HTTP/1.1 Connection: Keep-alive User-agent: hplip/2.0 Host: %s Content-length: %d Cache-control: No-cache %s""" % (url, self.http_host, len(s), s) log.log_data(data) self.writeEWS(data) ret = cStringIO.StringIO() while self.readEWS(4096, ret, timeout=5): pass ret = ret.getvalue() log.log_data(ret) self.closeEWS() match = http_result_pat.match(ret) try: code = int(match.group(1)) except (ValueError, TypeError): code = HTTP_ERROR return code == HTTP_OK def setPhoneNum(self, num): return self.post("/hp/device/set_config.html", {"FaxNumber": str(num)}) def getPhoneNum(self): stream = cStringIO.StringIO() self.getEWSUrl("/hp/device/settings_fax_setup_wizard.xml", stream) fax_setup = utils.XMLToDictParser().parseXML(stream.getvalue()) return fax_setup['faxsetupwizard-faxvoicenumber-faxnumber'] phone_num = property(getPhoneNum, setPhoneNum) def setStationName(self, name): return self.post("/hp/device/set_config.html", {"FaxCompanyName": str(name)}) def getStationName(self): stream = cStringIO.StringIO() self.getEWSUrl("/hp/device/settings_fax_setup_wizard.xml", stream) fax_setup = utils.XMLToDictParser().parseXML(stream.getvalue()) return fax_setup['faxsetupwizard-userinformation-faxcompanyname'] station_name = property(getStationName, setStationName) def setDateAndTime(self): stream = cStringIO.StringIO() self.getEWSUrl("/hp/device/settings_fax_setup_wizard.xml", stream) fax_setup = utils.XMLToDictParser().parseXML(stream.getvalue()) timeformat = fax_setup['faxsetupwizard-time-timeformat'] try: timeformat = int(timeformat) except (ValueError, TypeError): timeformat = TIME_FORMAT_AM_PM log.debug("timeformat: %d" % timeformat) dateformat = fax_setup['faxsetupwizard-date-dateformat'] try: dateformat = int(dateformat) except (ValueError, TypeError): dateformat = DATE_FORMAT_DD_MM_YYYY log.debug("dateformat: %d" % dateformat) t = time.localtime() hr = t[3] am_pm = PM if t[3] < 12: am_pm = AM if timeformat == TIME_FORMAT_AM_PM and hr > 12: hr -= 12 post = {"DateFormat" : dateformat, "Year" : t[0], "Month" : t[1], "Day" : t[2], "TimeFormat" : timeformat, "Hour" : hr, "Minute" : t[4]} if timeformat == TIME_FORMAT_AM_PM: post['AM'] = am_pm return self.post("/hp/device/set_config.html", post) def sendFaxes(self, phone_num_list, fax_file_list, cover_message='', cover_re='', cover_func=None, preserve_formatting=False, printer_name='', update_queue=None, event_queue=None): if not self.isSendFaxActive(): self.send_fax_thread = SOAPFaxSendThread(self, self.service, phone_num_list, fax_file_list, cover_message, cover_re, cover_func, preserve_formatting, printer_name, update_queue, event_queue) self.send_fax_thread.start() return True else: return False # **************************************************************************** # class SOAPFaxSendThread(FaxSendThread): def __init__(self, dev, service, phone_num_list, fax_file_list, cover_message='', cover_re='', cover_func=None, preserve_formatting=False, printer_name='', update_queue=None, event_queue=None): FaxSendThread.__init__(self, dev, service, phone_num_list, fax_file_list, cover_message, cover_re, cover_func, preserve_formatting, printer_name, update_queue, event_queue) self.job_id = utils.gen_random_uuid() log.debug("JobId: %s" % self.job_id) if dev.bus == 'net': self.http_host = "%s:8295" % self.dev.host else: self.http_host = 'localhost:8295' #self.http_host = 'localhost' def run(self): #results = {} # {'file' : error_code,...} STATE_DONE = 0 STATE_ABORTED = 10 STATE_SUCCESS = 20 STATE_BUSY = 25 STATE_READ_SENDER_INFO = 30 STATE_PRERENDER = 40 STATE_COUNT_PAGES = 50 STATE_NEXT_RECIPIENT = 60 STATE_COVER_PAGE = 70 STATE_SINGLE_FILE = 80 STATE_MERGE_FILES = 90 STATE_SINGLE_FILE = 100 STATE_SEND_FAX = 110 STATE_CLEANUP = 120 STATE_ERROR = 130 next_recipient = self.next_recipient_gen() state = STATE_READ_SENDER_INFO self.rendered_file_list = [] while state != STATE_DONE: # --------------------------------- Fax state machine if self.check_for_cancel(): state = STATE_ABORTED log.debug("STATE=(%d, 0, 0)" % state) if state == STATE_ABORTED: # --------------------------------- Aborted (10, 0, 0) log.error("Aborted by user.") self.write_queue((STATUS_IDLE, 0, '')) state = STATE_CLEANUP elif state == STATE_SUCCESS: # --------------------------------- Success (20, 0, 0) log.debug("Success.") self.write_queue((STATUS_COMPLETED, 0, '')) state = STATE_CLEANUP elif state == STATE_ERROR: # --------------------------------- Error (130, 0, 0) log.error("Error, aborting.") self.write_queue((STATUS_ERROR, 0, '')) state = STATE_CLEANUP elif state == STATE_BUSY: # --------------------------------- Busy (25, 0, 0) log.error("Device busy, aborting.") self.write_queue((STATUS_BUSY, 0, '')) state = STATE_CLEANUP elif state == STATE_READ_SENDER_INFO: # --------------------------------- Get sender info (30, 0, 0) log.debug("%s State: Get sender info" % ("*"*20)) state = STATE_PRERENDER try: try: self.dev.open() except Error, e: log.error("Unable to open device (%s)." % e.msg) state = STATE_ERROR else: try: self.sender_name = self.dev.station_name log.debug("Sender name=%s" % self.sender_name) self.sender_fax = self.dev.phone_num log.debug("Sender fax=%s" % self.sender_fax) except Error: log.error("HTTP GET failed!") state = STATE_ERROR finally: self.dev.close() elif state == STATE_PRERENDER: # --------------------------------- Pre-render non-G4 files (40, 0, 0) log.debug("%s State: Pre-render non-G4 files" % ("*"*20)) state = self.pre_render(STATE_COUNT_PAGES) elif state == STATE_COUNT_PAGES: # --------------------------------- Get total page count (50, 0, 0) log.debug("%s State: Get total page count" % ("*"*20)) state = self.count_pages(STATE_NEXT_RECIPIENT) elif state == STATE_NEXT_RECIPIENT: # --------------------------------- Loop for multiple recipients (60, 0, 0) log.debug("%s State: Next recipient" % ("*"*20)) state = STATE_COVER_PAGE try: recipient = next_recipient.next() log.debug("Processing for recipient %s" % recipient['name']) self.write_queue((STATUS_SENDING_TO_RECIPIENT, 0, recipient['name'])) except StopIteration: state = STATE_SUCCESS log.debug("Last recipient.") continue recipient_file_list = self.rendered_file_list[:] elif state == STATE_COVER_PAGE: # --------------------------------- Create cover page (70, 0, 0) log.debug("%s State: Render cover page" % ("*"*20)) state = self.cover_page(recipient) elif state == STATE_SINGLE_FILE: # --------------------------------- Special case for single file (no merge) (80, 0, 0) log.debug("%s State: Handle single file" % ("*"*20)) state = self.single_file(STATE_SEND_FAX) elif state == STATE_MERGE_FILES: # --------------------------------- Merge multiple G4 files (90, 0, 0) log.debug("%s State: Merge multiple files" % ("*"*20)) state = self.merge_files(STATE_SEND_FAX) elif state == STATE_SEND_FAX: # --------------------------------- Send fax state machine (110, 0, 0) log.debug("%s State: Send fax" % ("*"*20)) state = STATE_NEXT_RECIPIENT FAX_SEND_STATE_DONE = 0 FAX_SEND_STATE_ABORT = 10 FAX_SEND_STATE_ERROR = 20 FAX_SEND_STATE_BUSY = 25 FAX_SEND_STATE_SUCCESS = 30 FAX_SEND_STATE_DEVICE_OPEN = 40 FAX_SEND_STATE_BEGINJOB = 50 FAX_SEND_STATE_DOWNLOADPAGES = 60 FAX_SEND_STATE_ENDJOB = 70 FAX_SEND_STATE_CANCELJOB = 80 FAX_SEND_STATE_CLOSE_SESSION = 170 monitor_state = False fax_send_state = FAX_SEND_STATE_DEVICE_OPEN while fax_send_state != FAX_SEND_STATE_DONE: if self.check_for_cancel(): log.error("Fax send aborted.") fax_send_state = FAX_SEND_STATE_ABORT if monitor_state: fax_state = self.getFaxDownloadState() if not fax_state in (pml.UPDN_STATE_XFERACTIVE, pml.UPDN_STATE_XFERDONE): log.error("D/L error state=%d" % fax_state) fax_send_state = FAX_SEND_STATE_ERROR state = STATE_ERROR log.debug("STATE=(%d, %d, 0)" % (STATE_SEND_FAX, fax_send_state)) if fax_send_state == FAX_SEND_STATE_ABORT: # -------------- Abort (110, 10, 0) monitor_state = False fax_send_state = FAX_SEND_STATE_CANCELJOB state = STATE_ABORTED elif fax_send_state == FAX_SEND_STATE_ERROR: # -------------- Error (110, 20, 0) log.error("Fax send error.") monitor_state = False fax_send_state = FAX_SEND_STATE_CLOSE_SESSION state = STATE_ERROR elif fax_send_state == FAX_SEND_STATE_BUSY: # -------------- Busy (110, 25, 0) log.error("Fax device busy.") monitor_state = False fax_send_state = FAX_SEND_STATE_CLOSE_SESSION state = STATE_BUSY elif fax_send_state == FAX_SEND_STATE_SUCCESS: # -------------- Success (110, 30, 0) log.debug("Fax send success.") monitor_state = False fax_send_state = FAX_SEND_STATE_CLOSE_SESSION state = STATE_NEXT_RECIPIENT elif fax_send_state == FAX_SEND_STATE_DEVICE_OPEN: # -------------- Device open (110, 40, 0) log.debug("%s State: Open device" % ("*"*20)) fax_send_state = FAX_SEND_STATE_BEGINJOB try: self.dev.open() except Error, e: log.error("Unable to open device (%s)." % e.msg) fax_send_state = FAX_SEND_STATE_ERROR else: if self.dev.device_state == DEVICE_STATE_NOT_FOUND: fax_send_state = FAX_SEND_STATE_ERROR elif fax_send_state == FAX_SEND_STATE_BEGINJOB: # -------------- BeginJob (110, 50, 0) log.debug("%s State: BeginJob" % ("*"*20)) try: ff = file(self.f, 'r') except IOError: log.error("Unable to read fax file.") fax_send_state = FAX_SEND_STATE_ERROR continue try: header = ff.read(FILE_HEADER_SIZE) except IOError: log.error("Unable to read fax file.") fax_send_state = FAX_SEND_STATE_ERROR continue magic, version, total_pages, hort_dpi, vert_dpi, page_size, \ resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header) if magic != 'hplip_g3': log.error("Invalid file header. Bad magic.") fax_send_state = FAX_SEND_STATE_ERROR else: log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" % (magic, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding)) job_id = self.job_id delay = 0 faxnum = recipient['fax'].encode('ascii') speeddial = 0 if resolution == RESOLUTION_STD: res = "STANDARD" elif resolution == RESOLUTION_FINE: res = "FINE" elif resolution == RESOLUTION_300DPI: res = "SUPERFINE" soap = utils.cat( """$job_id$res$delay$faxnum$speeddial""") data = self.format_http(soap) log.log_data(data) if log.is_debug(): file('beginjob.log', 'w').write(data) self.dev.openSoapFax() self.dev.writeSoapFax(data) ret = cStringIO.StringIO() while self.dev.readSoapFax(8192, ret, timeout=5): pass ret = ret.getvalue() if log.is_debug(): file('beginjob_ret.log', 'w').write(ret) log.log_data(ret) self.dev.closeSoapFax() if self.get_error_code(ret) == HTTP_OK: fax_send_state = FAX_SEND_STATE_DOWNLOADPAGES else: fax_send_state = FAX_SEND_STATE_ERROR elif fax_send_state == FAX_SEND_STATE_DOWNLOADPAGES: # -------------- DownloadPages (110, 60, 0) log.debug("%s State: DownloadPages" % ("*"*20)) page = StringIO() for p in range(total_pages): if self.check_for_cancel(): fax_send_state = FAX_SEND_STATE_ABORT if fax_send_state == FAX_SEND_STATE_ABORT: break try: header = ff.read(PAGE_HEADER_SIZE) except IOError: log.error("Unable to read fax file.") fax_send_state = FAX_SEND_STATE_ERROR continue page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, reserved2 = \ self.decode_page_header(header) log.debug("Page=%d PPR=%d RPP=%d BPP=%d Thumb=%d" % (page_num, ppr, rpp, bytes_to_read, thumbnail_bytes)) if ppr != PIXELS_PER_LINE: log.error("Pixels per line (width) must be %d!" % PIXELS_PER_LINE) page.write(ff.read(bytes_to_read)) thumbnail = ff.read(thumbnail_bytes) # thrown away for now (should be 0 read) page.seek(0) try: data = page.read(bytes_to_read) except IOError: log.error("Unable to read fax file.") fax_send_state = FAX_SEND_STATE_ERROR break if data == '': log.error("No data!") fax_send_state = FAX_SEND_STATE_ERROR break height = rpp job_id = self.job_id soap = utils.cat( """$job_id$height""") m = dime.Message() m.add_record(dime.Record("cid:id0", "http://schemas.xmlsoap.org/soap/envelope/", dime.TYPE_T_URI, soap)) m.add_record(dime.Record("", "image/g4fax", dime.TYPE_T_MIME, data)) output = cStringIO.StringIO() m.generate(output) data = self.format_http(output.getvalue(), content_type="application/dime") log.log_data(data) if log.is_debug(): file('downloadpages%d.log' % p, 'w').write(data) try: self.dev.writeSoapFax(data) except Error: fax_send_state = FAX_SEND_STATE_ERROR ret = cStringIO.StringIO() try: while self.dev.readSoapFax(8192, ret, timeout=5): pass except Error: fax_send_state = FAX_SEND_STATE_ERROR ret = ret.getvalue() if log.is_debug(): file('downloadpages%d_ret.log' % p, 'w').write(ret) log.log_data(ret) self.dev.closeSoapFax() if self.get_error_code(ret) != HTTP_OK: fax_send_state = FAX_SEND_STATE_ERROR break page.truncate(0) page.seek(0) else: fax_send_state = FAX_SEND_STATE_ENDJOB elif fax_send_state == FAX_SEND_STATE_ENDJOB: # -------------- EndJob (110, 70, 0) log.debug("%s State: EndJob" % ("*"*20)) job_id = self.job_id soap = utils.cat( """$job_id$job_id""") data = self.format_http(soap) log.log_data(data) if log.is_debug(): file('endjob.log', 'w').write(data) self.dev.writeSoapFax(data) ret = cStringIO.StringIO() while self.dev.readSoapFax(8192, ret, timeout=5): pass ret = ret.getvalue() if log.is_debug(): file('endjob_ret.log', 'w').write(ret) log.log_data(ret) self.dev.closeSoapFax() if self.get_error_code(ret) == HTTP_OK: fax_send_state = FAX_SEND_STATE_SUCCESS else: fax_send_state = FAX_SEND_STATE_ERROR elif fax_send_state == FAX_SEND_STATE_CANCELJOB: # -------------- CancelJob (110, 80, 0) log.debug("%s State: CancelJob" % ("*"*20)) job_id = self.job_id soap = utils.cat( """$job_id$job_id""") data = self.format_http(soap) log.log_data(data) if log.is_debug(): file('canceljob.log', 'w').write(data) self.dev.writeSoapFax(data) ret = cStringIO.StringIO() while self.dev.readSoapFax(8192, ret, timeout=5): pass ret = ret.getvalue() if log.is_debug(): file('canceljob_ret.log', 'w').write(ret) log.log_data(ret) self.dev.closeSoapFax() if self.get_error_code(ret) == HTTP_OK: fax_send_state = FAX_SEND_STATE_CLOSE_SESSION else: fax_send_state = FAX_SEND_STATE_ERROR elif fax_send_state == FAX_SEND_STATE_CLOSE_SESSION: # -------------- Close session (110, 170, 0) log.debug("%s State: Close session" % ("*"*20)) log.debug("Closing session...") try: mm.close() except NameError: pass try: ff.close() except NameError: pass time.sleep(1) self.dev.closeSoapFax() self.dev.close() fax_send_state = FAX_SEND_STATE_DONE # Exit inner state machine elif state == STATE_CLEANUP: # --------------------------------- Cleanup (120, 0, 0) log.debug("%s State: Cleanup" % ("*"*20)) if self.remove_temp_file: log.debug("Removing merged file: %s" % self.f) try: os.remove(self.f) log.debug("Removed") except OSError: log.debug("Not found") state = STATE_DONE # Exit outer state machine def get_error_code(self, ret): if not ret: return HTTP_ERROR match = http_result_pat.match(ret) if match is None: return HTTP_OK try: code = int(match.group(1)) except (ValueError, TypeError): code = HTTP_ERROR return code def format_http(self, soap, content_type="text/xml; charset=utf-8"): host = self.http_host soap_len = len(soap) return utils.cat( """POST / HTTP/1.1\r Host: $host\r User-Agent: hplip/2.0\r Content-Type: $content_type\r Content-Length: $soap_len\r Connection: close\r SOAPAction: ""\r \r $soap""")