diff options
author | Jinkun Jang <jinkun.jang@samsung.com> | 2013-03-13 01:42:35 +0900 |
---|---|---|
committer | Jinkun Jang <jinkun.jang@samsung.com> | 2013-03-13 01:42:35 +0900 |
commit | 72835b3d805ac6c7cdaac7d3aff107567e938314 (patch) | |
tree | 0f2a04dc3d0672c0960a62804c6e7758673e393c /fax | |
parent | eb5e5ee9adb02776056d1b4494f66150a2fc45f1 (diff) | |
download | hplip-72835b3d805ac6c7cdaac7d3aff107567e938314.tar.gz hplip-72835b3d805ac6c7cdaac7d3aff107567e938314.tar.bz2 hplip-72835b3d805ac6c7cdaac7d3aff107567e938314.zip |
Tizen 2.1 base
Diffstat (limited to 'fax')
-rw-r--r-- | fax/__init__.py | 20 | ||||
-rwxr-xr-x | fax/backend/hpfax.py | 286 | ||||
-rw-r--r-- | fax/coverpages.py | 588 | ||||
-rw-r--r-- | fax/fax.py | 972 | ||||
-rw-r--r-- | fax/faxdevice.py | 70 | ||||
-rwxr-xr-x | fax/filters/pstotiff | 40 | ||||
-rw-r--r-- | fax/filters/pstotiff.convs | 27 | ||||
-rw-r--r-- | fax/filters/pstotiff.types | 53 | ||||
-rw-r--r-- | fax/ledmfax.py | 691 | ||||
-rw-r--r-- | fax/ledmsoapfax.py | 106 | ||||
-rw-r--r-- | fax/marvellfax.py | 872 | ||||
-rw-r--r-- | fax/pmlfax.py | 1026 | ||||
-rw-r--r-- | fax/ppd/HP-Fax-hpcups.ppd.gz | bin | 0 -> 980 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax-hpijs.ppd.gz | bin | 0 -> 1932 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax2-hpcups.ppd.gz | bin | 0 -> 988 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax2-hpijs.ppd.gz | bin | 0 -> 1935 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax3-hpcups.ppd.gz | bin | 0 -> 933 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax3-hpijs.ppd.gz | bin | 0 -> 1914 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax4-hpcups.ppd.gz | bin | 0 -> 946 bytes | |||
-rw-r--r-- | fax/ppd/HP-Fax4-hpijs.ppd.gz | bin | 0 -> 1897 bytes | |||
-rw-r--r-- | fax/soapfax.py | 719 |
21 files changed, 5470 insertions, 0 deletions
diff --git a/fax/__init__.py b/fax/__init__.py new file mode 100644 index 0000000..97d3c89 --- /dev/null +++ b/fax/__init__.py @@ -0,0 +1,20 @@ +# -*- 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 +# diff --git a/fax/backend/hpfax.py b/fax/backend/hpfax.py new file mode 100755 index 0000000..039af44 --- /dev/null +++ b/fax/backend/hpfax.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# (c) Copyright 2003-2009 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 +# + +__version__ = '4.1' +__title__ = 'CUPS Fax Backend (hpfax:)' +__doc__ = "CUPS backend for PC send fax. Generally this backend is run by CUPS, not directly by a user. To send a fax as a user, run hp-sendfax or print to the device's CUPS fax queue." + +# StdLib +import sys +import getopt +import ConfigParser +import os.path, os +import syslog +import time +import operator +import tempfile + + +CUPS_BACKEND_OK = 0 # Job completed successfully +CUPS_BACKEND_FAILED = 1 # Job failed, use error-policy +CUPS_BACKEND_AUTH_REQUIRED = 2 # Job failed, authentication required +CUPS_BACKEND_HOLD = 3 # Job failed, hold job +CUPS_BACKEND_STOP = 4 # Job failed, stop queue +CUPS_BACKEND_CANCEL = 5 # Job failed, cancel job + +PIPE_BUF = 4096 + +job_id = 0 +pid = os.getpid() +config_file = '/etc/hp/hplip.conf' +home_dir = '' + + +def bug(msg): + syslog.syslog("hpfax[%d]: error: %s\n" % (pid, msg)) + log.stderr("ERROR: %s\n" % msg) + + +if os.path.exists(config_file): + config = ConfigParser.ConfigParser() + config.read(config_file) + + try: + home_dir = config.get('dirs', 'home') + except: + bug("Error setting home directory: home= under [dirs] not found.") + sys.exit(1) +else: + bug("Error setting home directory: /etc/hp/hplip.conf not found") + sys.exit(1) + +if not home_dir or not os.path.exists(home_dir): + bug("Error setting home directory: Home directory %s not found." % home_dir) + sys.exit(1) + +sys.path.insert(0, home_dir) +os.chdir(home_dir) + +# HPLIP +try: + from base.g import * + from base.codes import * + from base import device + from base import utils + from prnt import cups +except ImportError, e: + bug("Error importing HPLIP modules: %s\n" % (pid, e)) + sys.exit(1) + +def handle_sigpipe(): + syslog.syslog("SIGPIPE!") + + +USAGE = [(__doc__, "", "para", True), + ("Usage: hpfax [job_id] [username] [title] [copies] [options]", "", "summary", True), + utils.USAGE_OPTIONS, + utils.USAGE_LOGGING1, utils.USAGE_LOGGING2, utils.USAGE_LOGGING3, + utils.USAGE_HELP, + ] + +def usage(typ='text'): + if typ == 'text': + utils.log_title(__title__, __version__) + + utils.format_text(USAGE, typ, title=__title__, crumb='hpfax:') + sys.exit(CUPS_BACKEND_OK) + +# Send dbus event to hpssd on dbus system bus +def send_message(device_uri, printer_name, event_code, username, job_id, title, pipe_name=''): + args = [device_uri, printer_name, event_code, username, job_id, title, pipe_name] + msg = lowlevel.SignalMessage('/', 'com.hplip.StatusService', 'Event') + msg.append(signature='ssisiss', *args) + + SystemBus().send_message(msg) + + +try: + opts, args = getopt.getopt(sys.argv[1:], 'l:hg', ['level=', 'help', 'help-rest', 'help-man']) + +except getopt.GetoptError: + usage() + +for o, a in opts: + + if o in ('-l', '--logging'): + log_level = a.lower().strip() + log.set_level(log_level) + + elif o == '-g': + log.set_level('debug') + + elif o in ('-h', '--help'): + usage() + + elif o == '--help-rest': + usage('rest') + + elif o == '--help-man': + usage('man') + + +if len( args ) == 0: + cups11 = utils.to_bool(sys_conf.get('configure', 'cups11', '0')) + + try: + probed_devices = device.probeDevices(['usb', 'par'], filter={'fax-type': (operator.gt, 0)}) + except Error: + sys.exit(CUPS_BACKEND_FAILED) + + good_devices = 0 + for uri in probed_devices: + try: + back_end, is_hp, bus, model, serial, dev_file, host, zc, port = \ + device.parseDeviceURI(uri) + except Error: + continue + + mq = device.queryModelByModel(model) + + if mq.get('fax-type', FAX_TYPE_NONE) in (FAX_TYPE_MARVELL,): + # HP Fax 3 + if bus == 'usb': + print 'direct %s "HP Fax 3" "%s USB %s HP Fax HPLIP" "MFG:HP;MDL:Fax 3;DES:HP Fax 3;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' '), serial) + + else: # par + print 'direct %s "HP Fax 3" "%s LPT HP Fax HPLIP" "MFG:HP;MDL:Fax 3;DES:HP Fax 3;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' ')) + + elif mq.get('fax-type', FAX_TYPE_NONE) in (FAX_TYPE_SOAP,) or mq.get('fax-type', FAX_TYPE_NONE) in (FAX_TYPE_LEDMSOAP,): + # HP Fax 2 + if bus == 'usb': + print 'direct %s "HP Fax 2" "%s USB %s HP Fax HPLIP" "MFG:HP;MDL:Fax 2;DES:HP Fax 2;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' '), serial) + + else: # par + print 'direct %s "HP Fax 2" "%s LPT HP Fax HPLIP" "MFG:HP;MDL:Fax 2;DES:HP Fax 2;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' ')) + elif mq.get('fax-type', FAX_TYPE_NONE) in (FAX_TYPE_LEDM,): + # HP Fax 4 + if bus == 'usb': + print 'direct %s "HP Fax 4" "%s USB %s HP Fax HPLIP" "MFG:HP;MDL:Fax 4;DES:HP Fax 4;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' '), serial) + + else: # par + print 'direct %s "HP Fax 4" "%s LPT HP Fax HPLIP" "MFG:HP;MDL:Fax 4;DES:HP Fax 4;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' ')) + + else: + # HP Fax + if bus == 'usb': + print 'direct %s "HP Fax" "%s USB %s HP Fax HPLIP" "MFG:HP;MDL:Fax;DES:HP Fax;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' '), serial) + + else: # par + print 'direct %s "HP Fax" "%s LPT HP Fax HPLIP" "MFG:HP;MDL:Fax;DES:HP Fax;"' % \ + (uri.replace("hp:", "hpfax:"), model.replace('_', ' ')) + + good_devices += 1 + + if good_devices == 0: + if cups11: + print 'direct hpfax:/no_device_found "HP Fax" "no_device_found" ""' + else: + print 'direct hpfax "Unknown" "HP Fax (HPLIP)" ""' + + sys.exit(CUPS_BACKEND_OK) + +else: + try: + # dBus + import dbus + from dbus import SystemBus, lowlevel + except ImportError: + bug("HPLIP pc send fax requires dbus and python-dbus") + sys.exit(CUPS_BACKEND_FAILED) + + import warnings + # Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters + # (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04) + warnings.simplefilter("ignore", DeprecationWarning) + + # CUPS provided environment + try: + device_uri = os.environ['DEVICE_URI'] + printer_name = os.environ['PRINTER'] + except KeyError: + bug("Improper environment: Must be run by CUPS.") + sys.exit(CUPS_BACKEND_FAILED) + + log.debug(args) + + try: + job_id, username, title, copies, options = args[0:5] + job_id = int(job_id) + except IndexError: + bug("Invalid command line: invalid arguments.") + sys.exit(CUPS_BACKEND_FAILED) + + send_message(device_uri, printer_name, EVENT_START_FAX_PRINT_JOB, username, job_id, title) + + try: + input_fd = file(args[5], 'r') + except IndexError: + input_fd = 0 + + # REVISIT: + tmp_dir = '/tmp' + pipe_name = os.path.join(tmp_dir, "hpfax-pipe-%d" % job_id) + + # Create the named pipe. Make sure it exists before sending + # message to hppsd. + os.umask(0111) + try: + os.mkfifo(pipe_name) + except OSError: + os.unlink(pipe_name) + os.mkfifo(pipe_name) + + # Send dbus event to hpssd + send_message(device_uri, printer_name, EVENT_FAX_RENDER_COMPLETE, username, job_id, title, pipe_name) + + # REVISIT: + pipe = os.open(pipe_name, os.O_WRONLY) + + bytes_read = 0 + while True: + data = os.read(input_fd, PIPE_BUF) + + if not data: + break + + os.write(pipe, data) + #syslog.syslog("Writing %d to pipe..." % len(data)) + bytes_read += len(data) + + if not bytes_read: + bug("No data on input file descriptor.") + sys.exit(CUPS_BACKEND_FAILED) + + os.close(input_fd) + os.close(pipe) + os.unlink(pipe_name) + + send_message(device_uri, printer_name, EVENT_END_FAX_PRINT_JOB, username, job_id, title) + + sys.exit(CUPS_BACKEND_OK) diff --git a/fax/coverpages.py b/fax/coverpages.py new file mode 100644 index 0000000..eb40ff7 --- /dev/null +++ b/fax/coverpages.py @@ -0,0 +1,588 @@ +# -*- coding: utf-8 -*- +# +# (c) Copyright 2003-2009 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 +# +import warnings +warnings.simplefilter("ignore", DeprecationWarning) +warnings.simplefilter("ignore", SyntaxWarning) +from reportlab.platypus.paragraph import Paragraph +from reportlab.platypus.flowables import Preformatted, Image, HRFlowable +from reportlab.platypus.doctemplate import * +#from reportlab.rl_config import TTFSearchPath +from reportlab.platypus import SimpleDocTemplate, Spacer +from reportlab.platypus.tables import Table, TableStyle +from reportlab.lib.pagesizes import letter, legal, A4 +from reportlab.lib.units import inch +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.lib import colors +#from reportlab.pdfbase import pdfmetrics +#from reportlab.pdfbase.ttfonts import TTFont +from time import localtime, strftime +#import warnings +warnings.simplefilter('default', DeprecationWarning) +warnings.simplefilter("default", SyntaxWarning) + +if __name__ == "__main__": + import sys + sys.path.append("..") + +from base.g import * +from base import utils + +PAGE_SIZE_LETTER = 'letter' +PAGE_SIZE_LEGAL = 'legal' +PAGE_SIZE_A4 = 'a4' + + +def escape(s): + return s.replace("&", "&").replace(">", ">").replace("<", "<") + + +def createStandardCoverPage(page_size=PAGE_SIZE_LETTER, + total_pages=1, + recipient_name='', + recipient_phone='', + recipient_fax='', + sender_name='', + sender_phone='', + sender_fax='', + sender_email='', + regarding='', + message='', + preserve_formatting=False, + output=None): + + s = getSampleStyleSheet() + + story = [] + + #print prop.locale + #TTFSearchPath.append('/usr/share/fonts/truetype/arphic') + #pdfmetrics.registerFont(TTFont('UMing', 'uming.ttf')) + + ps = ParagraphStyle(name="title", + parent=None, + fontName='helvetica-bold', + #fontName='STSong-Light', + #fontName = 'UMing', + fontSize=72, + ) + + story.append(Paragraph("FAX", ps)) + + story.append(Spacer(1, inch)) + + ps = ParagraphStyle(name='normal', + fontName='Times-Roman', + #fontName='STSong-Light', + #fontName='UMing', + fontSize=12) + + recipient_name_label = Paragraph("To:", ps) + recipient_name_text = Paragraph(escape(recipient_name[:64]), ps) + + recipient_fax_label = Paragraph("Fax:", ps) + recipient_fax_text = Paragraph(escape(recipient_fax[:64]), ps) + + recipient_phone_label = Paragraph("Phone:", ps) + recipient_phone_text = Paragraph(escape(recipient_phone[:64]), ps) + + sender_name_label = Paragraph("From:", ps) + sender_name_text = Paragraph(escape(sender_name[:64]), ps) + + sender_phone_label = Paragraph("Phone:", ps) + sender_phone_text = Paragraph(escape(sender_phone[:64]), ps) + + sender_email_label = Paragraph("Email:", ps) + sender_email_text = Paragraph(escape(sender_email[:64]), ps) + + regarding_label = Paragraph("Regarding:", ps) + regarding_text = Paragraph(escape(regarding[:128]), ps) + + date_time_label = Paragraph("Date:", ps) + date_time_text = Paragraph(strftime("%a, %d %b %Y %H:%M:%S (%Z)", localtime()), ps) + + total_pages_label = Paragraph("Total Pages:", ps) + total_pages_text = Paragraph("%d" % total_pages, ps) + + data = [[recipient_name_label, recipient_name_text, sender_name_label, sender_name_text], + [recipient_fax_label, recipient_fax_text, sender_phone_label, sender_phone_text], + [date_time_label, date_time_text, sender_email_label, sender_email_text], + [regarding_label, regarding_text, total_pages_label, total_pages_text]] + + LIST_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ]) + + story.append(HRFlowable(width='100%', color='black')) + + story.append(Table(data, style=LIST_STYLE)) + + if message: + MSG_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('SPAN', (-2, 1), (-1, -1)), + ]) + + story.append(HRFlowable(width='100%', color='black')) + story.append(Spacer(1, 0.5*inch)) + + if preserve_formatting: + message = '\n'.join(message[:2048].splitlines()[:32]) + + data = [[Paragraph("Comments/Notes:", ps), ''], + [Preformatted(escape(message), ps), ''],] + else: + data = [[Paragraph("Comments/Notes:", ps), ''], + [Paragraph(escape(message[:2048]), ps), ''],] + + story.append(HRFlowable(width='100%', color='black')) + story.append(Table(data, style=MSG_STYLE)) + story.append(HRFlowable(width='100%', color='black')) + + if page_size == PAGE_SIZE_LETTER: + pgsz = letter + elif page_size == PAGE_SIZE_LEGAL: + pgsz = legal + else: + pgsz = A4 + + if output is None: + f_fd, f = utils.make_temp_file() + else: + f = output + + doc = SimpleDocTemplate(f, pagesize=pgsz) + doc.build(story) + + return f + + +def createConfidentialCoverPage(page_size=PAGE_SIZE_LETTER, + total_pages=1, + recipient_name='', + recipient_phone='', + recipient_fax='', + sender_name='', + sender_phone='', + sender_fax='', + sender_email='', + regarding='', + message='', + preserve_formatting=False, + output=None): + + s = getSampleStyleSheet() + + story = [] + + story.append(Image(os.path.join(prop.image_dir, 'other', 'confidential_title.png'))) + story.append(Spacer(1, inch)) + story.append(HRFlowable(width='100%', color='black')) + + ps = ParagraphStyle(name='normal', + fontName='Times-Roman', + #fontName='STSong-Light', + #fontName='UMing', + fontSize=12) + + recipient_name_label = Paragraph("To:", ps) + recipient_name_text = Paragraph(escape(recipient_name[:64]), ps) + + recipient_fax_label = Paragraph("Fax:", ps) + recipient_fax_text = Paragraph(escape(recipient_fax[:64]), ps) + + recipient_phone_label = Paragraph("Phone:", ps) + recipient_phone_text = Paragraph(escape(recipient_phone[:64]), ps) + + sender_name_label = Paragraph("From:", ps) + sender_name_text = Paragraph(escape(sender_name[:64]), ps) + + sender_phone_label = Paragraph("Phone:", ps) + sender_phone_text = Paragraph(escape(sender_phone[:64]), ps) + + sender_email_label = Paragraph("Email:", ps) + sender_email_text = Paragraph(escape(sender_email[:64]), ps) + + regarding_label = Paragraph("Regarding:", ps) + regarding_text = Paragraph(escape(regarding[:128]), ps) + + date_time_label = Paragraph("Date:", ps) + date_time_text = Paragraph(strftime("%a, %d %b %Y %H:%M:%S (%Z)", localtime()), ps) + + total_pages_label = Paragraph("Total Pages:", ps) + total_pages_text = Paragraph("%d" % total_pages, ps) + + data = [[recipient_name_label, recipient_name_text], + [recipient_fax_label, recipient_fax_text], + ['', ''], + [sender_name_label, sender_name_text], + [sender_phone_label, sender_phone_text], + [sender_email_label, sender_email_text], + ['', ''], + [date_time_label, date_time_text], + [total_pages_label, total_pages_text], + [regarding_label, regarding_text],] + + LIST_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ]) + + + story.append(Table(data, style=LIST_STYLE)) + story.append(HRFlowable(width='100%', color='black')) + + if message: + MSG_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + #('SPAN', (-2, 1), (-1, -1)), + ]) + + #story.append(HRFlowable(width='100%', color='black')) + story.append(Spacer(1, 0.5*inch)) + +# if preserve_formatting: +# message = '\n'.join(message[:2048].splitlines()[:32]) +# +# data = [#[Paragraph("Comments/Notes:", ps), ''], +# [Preformatted(escape(message), ps)],] +# else: +# data = [#[Paragraph("Comments/Notes:", ps), ''], +# [Paragraph(escape(message[:2048]), ps), ''],] +# +# #story.append(HRFlowable(width='100%', color='black')) +# #story.append(Table(data, style=MSG_STYLE)) + + if preserve_formatting: + message = '\n'.join(message[:2048].splitlines()[:32]) + story.append(Preformatted(escape(message), ps)) + else: + story.append(Paragraph(escape(message), ps)) + + + if page_size == PAGE_SIZE_LETTER: + pgsz = letter + elif page_size == PAGE_SIZE_LEGAL: + pgsz = legal + else: + pgsz = A4 + + if output is None: + f_fd, f = utils.make_temp_file() + else: + f = output + + doc = SimpleDocTemplate(f, pagesize=pgsz) + doc.build(story) + + return f + + +def createGenericCoverPage(page_size=PAGE_SIZE_LETTER, + total_pages=1, + recipient_name='', + recipient_phone='', + recipient_fax='', + sender_name='', + sender_phone='', + sender_fax='', + sender_email='', + regarding='', + message='', + preserve_formatting=False, + output=None): + + s = getSampleStyleSheet() + + story = [] + + i = Image(os.path.join(prop.image_dir, 'other', 'generic_title.png'), width=250, height=147) + i.hAlign = 'LEFT' + story.append(i) + #story.append(Spacer(1, inch)) + story.append(HRFlowable(width='100%', color='black')) + + ps = ParagraphStyle(name='normal', + fontName='Times-Roman', + #fontName='STSong-Light', + #fontName='UMing', + fontSize=12) + + recipient_name_label = Paragraph("To:", ps) + recipient_name_text = Paragraph(escape(recipient_name[:64]), ps) + + recipient_fax_label = Paragraph("Fax:", ps) + recipient_fax_text = Paragraph(escape(recipient_fax[:64]), ps) + + recipient_phone_label = Paragraph("Phone:", ps) + recipient_phone_text = Paragraph(escape(recipient_phone[:64]), ps) + + sender_name_label = Paragraph("From:", ps) + sender_name_text = Paragraph(escape(sender_name[:64]), ps) + + sender_phone_label = Paragraph("Phone:", ps) + sender_phone_text = Paragraph(escape(sender_phone[:64]), ps) + + sender_email_label = Paragraph("Email:", ps) + sender_email_text = Paragraph(escape(sender_email[:64]), ps) + + regarding_label = Paragraph("Regarding:", ps) + regarding_text = Paragraph(escape(regarding[:128]), ps) + + date_time_label = Paragraph("Date:", ps) + date_time_text = Paragraph(strftime("%a, %d %b %Y %H:%M:%S (%Z)", localtime()), ps) + + total_pages_label = Paragraph("Total Pages:", ps) + total_pages_text = Paragraph("%d" % total_pages, ps) + + data = [[recipient_name_label, recipient_name_text], + [recipient_fax_label, recipient_fax_text], + ['', ''], + [sender_name_label, sender_name_text], + [sender_phone_label, sender_phone_text], + [sender_email_label, sender_email_text], + ['', ''], + [date_time_label, date_time_text], + [total_pages_label, total_pages_text], + [regarding_label, regarding_text],] + + LIST_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ]) + + + story.append(Table(data, style=LIST_STYLE)) + story.append(HRFlowable(width='100%', color='black')) + + if message: + MSG_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + #('SPAN', (-2, 1), (-1, -1)), + ]) + + #story.append(HRFlowable(width='100%', color='black')) + story.append(Spacer(1, 0.5*inch)) + +# if preserve_formatting: +# message = '\n'.join(message[:2048].splitlines()[:32]) +# +# data = [#[Paragraph("Comments/Notes:", ps), ''], +# [Preformatted(escape(message), ps)],] +# else: +# data = [#[Paragraph("Comments/Notes:", ps), ''], +# [Paragraph(escape(message[:2048]), ps), ''],] +# +# #story.append(HRFlowable(width='100%', color='black')) +# #story.append(Table(data, style=MSG_STYLE)) + + if preserve_formatting: + message = '\n'.join(message[:2048].splitlines()[:32]) + story.append(Preformatted(escape(message), ps)) + else: + story.append(Paragraph(escape(message), ps)) + + # + + if page_size == PAGE_SIZE_LETTER: + pgsz = letter + elif page_size == PAGE_SIZE_LEGAL: + pgsz = legal + else: + pgsz = A4 + + if output is None: + f_fd, f = utils.make_temp_file() + else: + f = output + + doc = SimpleDocTemplate(f, pagesize=pgsz) + doc.build(story) + + return f + + +def createUrgentCoverPage(page_size=PAGE_SIZE_LETTER, + total_pages=1, + recipient_name='', + recipient_phone='', + recipient_fax='', + sender_name='', + sender_phone='', + sender_fax='', + sender_email='', + regarding='', + message='', + preserve_formatting=False, + output=None): + + s = getSampleStyleSheet() + + story = [] + i = Image(os.path.join(prop.image_dir, 'other', 'urgent_title.png'), width=424, height=92) + i.hAlign = 'LEFT' + story.append(i) + story.append(Spacer(1, inch)) + story.append(HRFlowable(width='100%', color='black')) + + ps = ParagraphStyle(name='normal', + fontName='Times-Roman', + #fontName='STSong-Light', + #fontName='UMing', + fontSize=12) + + recipient_name_label = Paragraph("To:", ps) + recipient_name_text = Paragraph(escape(recipient_name[:64]), ps) + + recipient_fax_label = Paragraph("Fax:", ps) + recipient_fax_text = Paragraph(escape(recipient_fax[:64]), ps) + + recipient_phone_label = Paragraph("Phone:", ps) + recipient_phone_text = Paragraph(escape(recipient_phone[:64]), ps) + + sender_name_label = Paragraph("From:", ps) + sender_name_text = Paragraph(escape(sender_name[:64]), ps) + + sender_phone_label = Paragraph("Phone:", ps) + sender_phone_text = Paragraph(escape(sender_phone[:64]), ps) + + sender_email_label = Paragraph("Email:", ps) + sender_email_text = Paragraph(escape(sender_email[:64]), ps) + + regarding_label = Paragraph("Regarding:", ps) + regarding_text = Paragraph(escape(regarding[:128]), ps) + + date_time_label = Paragraph("Date:", ps) + date_time_text = Paragraph(strftime("%a, %d %b %Y %H:%M:%S (%Z)", localtime()), ps) + + total_pages_label = Paragraph("Total Pages:", ps) + total_pages_text = Paragraph("%d" % total_pages, ps) + + data = [[recipient_name_label, recipient_name_text], + [recipient_fax_label, recipient_fax_text], + ['', ''], + [sender_name_label, sender_name_text], + [sender_phone_label, sender_phone_text], + [sender_email_label, sender_email_text], + ['', ''], + [date_time_label, date_time_text], + [total_pages_label, total_pages_text], + [regarding_label, regarding_text],] + + LIST_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ]) + + + story.append(Table(data, style=LIST_STYLE)) + story.append(HRFlowable(width='100%', color='black')) + + if message: + MSG_STYLE = TableStyle([#('LINEABOVE', (0,0), (-1,0), 2, colors.black), + #('LINEABOVE', (0,1), (-1,-1), 0.25, colors.black), + #('LINEBELOW', (0,-1), (-1,-1), 2, colors.black), + ('ALIGN', (1,1), (-1,-1), 'RIGHT'), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + #('SPAN', (-2, 1), (-1, -1)), + ]) + + #story.append(HRFlowable(width='100%', color='black')) + story.append(Spacer(1, 0.5*inch)) + +# if preserve_formatting: +# message = '\n'.join(message[:2048].splitlines()[:32]) +# +# data = [#[Paragraph("Comments/Notes:", ps), ''], +# [Preformatted(escape(message), ps)],] +# else: +# data = [#[Paragraph("Comments/Notes:", ps), ''], +# [Paragraph(escape(message[:2048]), ps), ''],] +# +# #story.append(HRFlowable(width='100%', color='black')) +# #story.append(Table(data, style=MSG_STYLE)) + + if preserve_formatting: + message = '\n'.join(message[:2048].splitlines()[:32]) + story.append(Preformatted(escape(message), ps)) + else: + story.append(Paragraph(escape(message), ps)) + + + if page_size == PAGE_SIZE_LETTER: + pgsz = letter + elif page_size == PAGE_SIZE_LEGAL: + pgsz = legal + else: + pgsz = A4 + + if output is None: + f_fd, f = utils.make_temp_file() + else: + f = output + + doc = SimpleDocTemplate(f, pagesize=pgsz) + doc.build(story) + + return f + + +# { "name" : (function, "thumbnail.png"), ... } +COVERPAGES = { "basic": (createStandardCoverPage, 'standard_coverpage.png'), + "confidential": (createConfidentialCoverPage, 'confidential_coverpage.png'), + "generic": (createGenericCoverPage, "generic_coverpage.png"), + "urgent": (createUrgentCoverPage, "urgent_coverpage.png"), + } + + +if __name__ == "__main__": + createUrgentCoverPage(page_size=PAGE_SIZE_LETTER, + total_pages=1, + recipient_name='Trex', + recipient_phone='+1 234-567-8912', + recipient_fax='+1 432 123 1234', + sender_name='Don', + sender_phone='+1 234 432 1234', + sender_fax='+1 567 876 5123 ', + sender_email='test@hplip.sf.net', + regarding='Some sorta stuff', + message="""Some HP printers require proprietary software technologies to allow full access to printer features and performance. These technologies cannot be open sourced. Because of this, HP uses a binary plug-in for these printers that work in conjunction with our Linux Open Source Printing Software to improve the printing experience for HP’s Linux Printing Customers. This binary plug-in requires the user to read and agree to a license agreement at the time of driver installation. There is a single plug-in file (for each HPLIP release) for all plug-in enabled devices.""", + preserve_formatting=False, + output="output.pdf") + + diff --git a/fax/fax.py b/fax/fax.py new file mode 100644 index 0000000..58a0e29 --- /dev/null +++ b/fax/fax.py @@ -0,0 +1,972 @@ +# -*- coding: utf-8 -*- +# +# (c) Copyright 2010 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 generators + +# Std Lib +import sys +import os +import threading +import cPickle +import time +from cStringIO import StringIO +import struct + +# Local +from base.g import * +from base.codes import * +from base.ldif import LDIFParser +from base import device, utils, vcard +from prnt import cups + +try: + import coverpages +except ImportError: + pass + +try: + import dbus +except ImportError: + log.error("dbus is required for PC send fax.") + +import warnings +# Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters +# (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04) +warnings.simplefilter("ignore", DeprecationWarning) + + +# Update queue values (Send thread ==> UI) +STATUS_IDLE = 0 +STATUS_PROCESSING_FILES = 1 +STATUS_SENDING_TO_RECIPIENT = 2 +STATUS_DIALING = 3 +STATUS_CONNECTING = 4 +STATUS_SENDING = 5 +STATUS_COMPLETED = 6 +STATUS_CREATING_COVER_PAGE = 7 +STATUS_ERROR = 8 +STATUS_BUSY = 9 +STATUS_CLEANUP = 10 +STATUS_ERROR_IN_CONNECTING = 11 +STATUS_ERROR_IN_TRANSMITTING = 12 +STATUS_ERROR_PROBLEM_IN_FAXLINE = 13 +STATUS_JOB_CANCEL = 14 + +# Event queue values (UI ==> Send thread) +EVENT_FAX_SEND_CANCELED = 1 +# Other values in queue are: +#EVENT_FAX_RENDER_COMPLETE_BEGIN = 8010 +#EVENT_FAX_RENDER_COMPLETE_SENDDATA = 8011 +#EVENT_FAX_RENDER_COMPLETE_END = 8012 + +# **************************************************************************** # +# HPLIP G3 Fax File Format (big endian) +# +# #==============================================# +# # File Header: Total 28 bytes # +# #..............................................# +# # Magic bytes: 8 bytes ("hplip_g3") # +# # Format version: 8 bits (1) # +# # Total pages in file(=p): 32 bits # +# # Hort DPI: 16 bits (200 or 300) # +# # Vert DPI: 16 bits (100, 200, or 300) # +# # Page Size: 8 bits (0=Unk, 1=Letter, 2=A4, # +# # 3=Legal) # +# # Resolution: 8 bits (0=Unk, 1=Std, 2=Fine, # +# # 3=300DPI) # +# # Encoding: 8 bits (2=MH, 4=MMR, 7=JPEG) # +# # Reserved1: 32 bits (0) # +# # Reserved2: 32 bits (0) # +# #----------------------------------------------# +# # Page 1 Header: Total 24 bytes # +# #..............................................# +# # Page number: 32 bits (1 based) # +# # Pixels per row: 32 bits # +# # Rows this page: 32 bits # +# # Image bytes this page(=x): 32 bits # +# # Thumbnail bytes this page(=y): 32 bits # +# # (thumbnail not present if y == 0) # +# # (encoding?) # +# # letter: 134 px wide x 173 px high # +# # legal: 134 px wide x 221 px high # +# # a4 : 134 px wide x 190 px high # +# # Reserved3: 32 bits (0) # +# #..............................................# +# # Image data: x bytes # +# #..............................................# +# # Thumbnail data: y bytes (if present) # +# #----------------------------------------------# +# # Page 2 Header: Total 24 bytes # +# #..............................................# +# # Image Data # +# #..............................................# +# # Thumbnail data (if present) # +# #----------------------------------------------# +# # ... Pages 3 - (p-1) ... # +# #----------------------------------------------# +# # Page p Header: Total 24 bytes # +# #..............................................# +# # Image Data # +# #..............................................# +# # Thumbnail data (if present) # +# #==============================================# +# + +RESOLUTION_STD = 1 +RESOLUTION_FINE = 2 +RESOLUTION_300DPI = 3 + +FILE_HEADER_SIZE = 28 +PAGE_HEADER_SIZE = 24 +# **************************************************************************** # + +##skip_dn = ["uid=foo,ou=People,dc=example,dc=com", +## "uid=bar,ou=People,dc=example,dc=com", "dc=example,dc=com"] + +class FaxLDIFParser(LDIFParser): + def __init__(self, input, db): + LDIFParser.__init__(self, input) + self.db = db + + def handle(self, dn, entry): + if dn: + try: + firstname = entry['givenName'][0] + except KeyError: + try: + firstname = entry['givenname'][0] + except KeyError: + firstname = '' + + try: + lastname = entry['sn'][0] + except KeyError: + lastname = '' + + try: + nickname = entry['cn'][0] + except KeyError: + nickname = firstname + ' ' + lastname + + try: + fax = entry['facsimiletelephonenumber'][0] # fax + except KeyError: + try: + fax = entry['fax'][0] + except KeyError: + fax = '' + + grps = [] + try: + grps = entry['ou'] + except KeyError: + pass + + grps.append(u'All') + groups = [g for g in grps if g] + + if nickname: + log.debug("Import: name=%s, fax=%s, group(s)=%s, notes=%s" % ( nickname, fax, ','.join(groups), dn)) + self.db.set(nickname, title, firstname, lastname, fax, groups, dn) + + + +# **************************************************************************** # +class FaxAddressBook(object): # Pickle based address book + def __init__(self): + self._data = {} + # + # { 'name' : {'name': u'', + # 'firstname' : u'', # NOT USED STARTING IN 2.8.9 + # 'lastname': u', # NOT USED STARTING IN 2.8.9 + # 'title' : u'', # NOT USED STARTING IN 2.8.9 + # 'fax': u'', + # 'groups' : [u'', u'', ...], + # 'notes' : u'', } ... + # } + # + self.load() + + def load(self): + self._fab = "/dev/null" + if prop.user_dir != None: + self._fab = os.path.join(prop.user_dir, "fab.pickle") + #old_fab = os.path.join(prop.user_dir, "fab.db") + + # Load the existing pickle if present + if os.path.exists(self._fab): + pickle_file = open(self._fab, "r") + self._data = cPickle.load(pickle_file) + pickle_file.close() + else: + self.save() # save the empty file to create the file + + + def set(self, name, title, firstname, lastname, fax, groups, notes): + try: + grps = [unicode(s) for s in groups] + except UnicodeDecodeError: + grps = [unicode(s.decode('utf-8')) for s in groups] + + self._data[unicode(name)] = {'name' : unicode(name), + 'title': unicode(title), # NOT USED STARTING IN 2.8.9 + 'firstname': unicode(firstname), # NOT USED STARTING IN 2.8.9 + 'lastname': unicode(lastname), # NOT USED STARTING IN 2.8.9 + 'fax': unicode(fax), + 'notes': unicode(notes), + 'groups': grps} + + self.save() + + insert = set + + + def set_key_value(self, name, key, value): + self._data[unicode(name)][key] = value + self.save() + + + def get(self, name): + return self._data.get(name, None) + + select = get + + def rename(self, old_name, new_name): + try: + self._data[old_name] + except KeyError: + return + else: + try: + self._data[new_name] + except KeyError: + self._data[new_name] = self._data[old_name].copy() + self._data[new_name]['name'] = new_name + del self._data[old_name] + self.save() + + + def get_all_groups(self): + all_groups = [] + for e, v in self._data.items(): + for g in v['groups']: + if g not in all_groups: + all_groups.append(g) + return all_groups + + + def get_all_records(self): + return self._data + + + def get_all_names(self): + return self._data.keys() + + + def save(self): + try: + pickle_file = open(self._fab, "w") + cPickle.dump(self._data, pickle_file, cPickle.HIGHEST_PROTOCOL) + pickle_file.close() + except IOError: + log.error("I/O error saving fab file.") + + + def clear(self): + self._data = {} + self.save() + + + def delete(self, name): + if name in self._data: + del self._data[name] + self.save() + return True + + return False + + + def last_modification_time(self): + try: + return os.stat(self._fab).st_mtime + except OSError: + return 0 + + + def update_groups(self, group, members): + for e, v in self._data.items(): + if v['name'] in members: # membership indicated + if not group in v['groups']: + v['groups'].append(unicode(group)) + else: + if group in v['groups']: + v['groups'].remove(unicode(group)) + self.save() + + + def delete_group(self, group): + for e, v in self._data.items(): + if group in v['groups']: + v['groups'].remove(unicode(group)) + self.save() + + + def group_members(self, group): + members = [] + for e, v in self._data.items(): + if group in v['groups']: + members.append(e) + return members + + + def add_to_group(self, group, members): + group_members = self.group_members(group) + new_group_members = [] + for m in members: + if m not in group_members: + new_group_members.append(m) + + self.update_groups(group, group_members + new_group_members) + + + def remove_from_group(self, group, remove_members): + group_members = self.group_members(group) + new_group_members = [] + for m in group_members: + if m not in remove_members: + new_group_members.append(m) + + self.update_groups(group, new_group_members) + + + def rename_group(self, old_group, new_group): + members = self.group_members(old_group) + self.update_groups(old_group, []) + self.update_groups(new_group, members) + + + def import_ldif(self, filename): + try: + data = open(filename, 'r').read() + log.debug_block(filename, data) + parser = FaxLDIFParser(open(filename, 'r'), self) + parser.parse() + self.save() + return True, '' + except ValueError, e: + return False, e.message + + + def import_vcard(self, filename): + data = file(filename, 'r').read() + log.debug_block(filename, data) + + for card in vcard.VCards(vcard.VFile(vcard.opentextfile(filename))): + log.debug(card) + + if card['name']: + fax = '' + for x in range(1, 9999): + if x == 1: + s = 'phone' + else: + s = 'phone%d' % x + + try: + card[s] + except KeyError: + break + else: + if 'fax' in card[s]['type']: + fax = card[s]['number'] + break + + org = card.get('organisation', '') + if org: + org = [org] + else: + org = card.get('categories', '').split(';') + if not org: + org = [] + + org.append(u'All') + groups = [o for o in org if o] + + name = card['name'] + notes = card.get('notes', u'') + log.debug("Import: name=%s, fax=%s group(s)=%s notes=%s" % (name, fax, ','.join(groups), notes)) + self.set(name, u'', u'', u'', fax, groups, notes) + + return True, '' + + +# **************************************************************************** # +class FaxDevice(device.Device): + + def __init__(self, device_uri=None, printer_name=None, + callback=None, + fax_type=FAX_TYPE_NONE, + disable_dbus=False): + + device.Device.__init__(self, device_uri, printer_name, + None, callback, disable_dbus) + + self.send_fax_thread = None + self.upload_log_thread = None + self.fax_type = fax_type + + if not disable_dbus: + session_bus = dbus.SessionBus() + self.service = session_bus.get_object('com.hplip.StatusService', "/com/hplip/StatusService") + else: + self.service = None + + + def setPhoneNum(self, num): + raise AttributeError + + def getPhoneNum(self): + raise AttributeError + + phone_num = property(getPhoneNum, setPhoneNum) + + + def setStationName(self, name): + raise AttributeError + + def getStationName(self): + raise AttributeError + + station_name = property(getStationName, setStationName) + + def setDateAndTime(self): + raise AttributeError + + def uploadLog(self): + raise AttributeError + + def isUploadLogActive(self): + raise AttributeError + + def waitForUploadLogThread(self): + raise AttributeError + + 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): + + raise AttributeError + + def isSendFaxActive(self): + if self.send_fax_thread is not None: + return self.send_fax_thread.isAlive() + else: + return False + + def waitForSendFaxThread(self): + if self.send_fax_thread is not None and \ + self.send_fax_thread.isAlive(): + + try: + self.send_fax_thread.join() + except KeyboardInterrupt: + pass + + +# **************************************************************************** # + + +def getFaxDevice(device_uri=None, printer_name=None, + callback=None, + fax_type=FAX_TYPE_NONE, + disable_dbus=False): + + if fax_type == FAX_TYPE_NONE: + if device_uri is None and printer_name is not None: + printers = cups.getPrinters() + + for p in printers: + if p.name.lower() == printer_name.lower(): + device_uri = p.device_uri + break + else: + raise Error(ERROR_DEVICE_NOT_FOUND) + + if device_uri is not None: + mq = device.queryModelByURI(device_uri) + fax_type = mq['fax-type'] + + log.debug("fax-type=%d" % fax_type) + + if fax_type in (FAX_TYPE_BLACK_SEND_EARLY_OPEN, FAX_TYPE_BLACK_SEND_LATE_OPEN): + from pmlfax import PMLFaxDevice + return PMLFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_SOAP: + from soapfax import SOAPFaxDevice + return SOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_LEDMSOAP: + from ledmsoapfax import LEDMSOAPFaxDevice + return LEDMSOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_MARVELL: + from marvellfax import MarvellFaxDevice + return MarvellFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_LEDM: + from ledmfax import LEDMFaxDevice + return LEDMFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + else: + raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION) + +# **************************************************************************** # + + + + +# TODO: Define these in only 1 place! +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 + +class FaxSendThread(threading.Thread): + 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): + + threading.Thread.__init__(self) + + self.dev = dev # device.Device + self.service = service # dbus proxy to status server object + self.phone_num_list = phone_num_list + self.fax_file_list = fax_file_list + self.update_queue = update_queue + self.event_queue = event_queue + self.cover_message = cover_message + self.cover_re = cover_re + self.cover_func = cover_func + self.current_printer = printer_name + self.stream = StringIO() + self.prev_update = '' + self.remove_temp_file = False + self.preserve_formatting = preserve_formatting + self.results = {} # {'file' : error_code,...} + self.cover_page_present = False + self.recipient_file_list = [] + self.f = None # final file of fax data to send (pages merged) + self.job_hort_dpi = 0 + self.job_hort_dpi = 0 + self.job_vert_dpi = 0 + self.job_page_size = 0 + self.job_resolution = 0 + self.job_encoding = 0 + + + def pre_render(self, state): + # pre-render each page that needs rendering + # except for the cover page + self.cover_page_present = False + log.debug(self.fax_file_list) + + for fax_file in self.fax_file_list: # (file, type, desc, title) + fax_file_name, fax_file_type, fax_file_desc, \ + fax_file_title, fax_file_pages = fax_file + + if fax_file_type == "application/hplip-fax-coverpage": # render later + self.cover_page_present = True + log.debug("Skipping coverpage") + + #if fax_file_type == "application/hplip-fax": # already rendered + else: + self.rendered_file_list.append((fax_file_name, "application/hplip-fax", + "HP Fax", fax_file_title)) + + log.debug("Processing pre-rendered file: %s (%d pages)" % + (fax_file_name, fax_file_pages)) + + if self.check_for_cancel(): + state = STATE_ABORTED + + log.debug(self.rendered_file_list) + + if self.check_for_cancel(): + state = STATE_ABORTED + + return state + + + def count_pages(self, state): + self.recipient_file_list = self.rendered_file_list[:] + log.debug("Counting total pages...") + self.job_total_pages = 0 + log.debug(self.recipient_file_list) + + i = 0 + for fax_file in self.recipient_file_list: # (file, type, desc, title) + fax_file_name = fax_file[0] + log.debug("Processing file (counting pages): %s..." % fax_file_name) + + #self.write_queue((STATUS_PROCESSING_FILES, self.job_total_pages, '')) + + if os.path.exists(fax_file_name): + self.results[fax_file_name] = ERROR_SUCCESS + fax_file_fd = file(fax_file_name, 'r') + header = fax_file_fd.read(FILE_HEADER_SIZE) + + 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.") + self.results[fax_file_name] = ERROR_FAX_INVALID_FAX_FILE + state = STATE_ERROR + continue + + if not i: + self.job_hort_dpi, self.job_vert_dpi, self.job_page_size, \ + self.job_resolution, self.job_encoding = \ + hort_dpi, vert_dpi, page_size, resolution, encoding + + i += 1 + else: + if self.job_hort_dpi != hort_dpi or \ + self.job_vert_dpi != vert_dpi or \ + self.job_page_size != page_size or \ + self.job_resolution != resolution or \ + self.job_encoding != encoding: + + log.error("Incompatible options for file: %s" % fax_file_name) + self.results[fax_file_name] = ERROR_FAX_INCOMPATIBLE_OPTIONS + state = STATE_ERROR + + + 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)) + + self.job_total_pages += total_pages + + fax_file_fd.close() + + else: + log.error("Unable to find HP Fax file: %s" % fax_file_name) + self.results[fax_file_name] = ERROR_FAX_FILE_NOT_FOUND + state = STATE_ERROR + break + + if self.check_for_cancel(): + state = STATE_ABORTED + break + + + if self.cover_page_present: + self.job_total_pages += 1 # Cover pages are truncated to 1 page + + log.debug("Total fax pages=%d" % self.job_total_pages) + + return state + + def decode_fax_header(self, header): + try: + return struct.unpack(">8sBIHHBBBII", header) + except struct.error: + return -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + + def decode_page_header(self, header): + try: + return struct.unpack(">IIIIII", header) + except struct.error: + return -1, -1, -1, -1, -1, -1 + + def cover_page(self, recipient): + if self.job_total_pages > 1: + state = STATE_MERGE_FILES + else: + state = STATE_SINGLE_FILE + + if self.cover_page_present: + log.debug("Creating cover page for recipient: %s" % recipient['name']) + fax_file, canceled = self.render_cover_page(recipient) + + if canceled: + state = STATE_ABORTED + elif not fax_file: + state = STATE_ERROR # timeout + else: + self.recipient_file_list.insert(0, (fax_file, "application/hplip-fax", + "HP Fax", 'Cover Page')) + + log.debug("Cover page G3 file: %s" % fax_file) + + self.results[fax_file] = ERROR_SUCCESS + + return state + + def single_file(self, state): + state = STATE_SEND_FAX + + log.debug("Processing single file...") + self.f = self.recipient_file_list[0][0] + + try: + f_fd = file(self.f, 'r') + except IOError: + log.error("Unable to open fax file: %s" % self.f) + state = STATE_ERROR + else: + header = f_fd.read(FILE_HEADER_SIZE) + + magic, version, total_pages, hort_dpi, vert_dpi, page_size, \ + resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header) + + self.results[self.f] = ERROR_SUCCESS + + if magic != 'hplip_g3': + log.error("Invalid file header. Bad magic.") + self.results[self.f] = ERROR_FAX_INVALID_FAX_FILE + state = STATE_ERROR + + 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)) + + f_fd.close() + + return state + + + def merge_files(self, state): + log.debug("%s State: Merge multiple files" % ("*"*20)) + log.debug(self.recipient_file_list) + log.debug("Merging g3 files...") + self.remove_temp_file = True + + if self.job_total_pages: + f_fd, self.f = utils.make_temp_file() + log.debug("Temp file=%s" % self.f) + + data = struct.pack(">8sBIHHBBBII", "hplip_g3", 1L, self.job_total_pages, + self.job_hort_dpi, self.job_vert_dpi, self.job_page_size, + self.job_resolution, self.job_encoding, + 0L, 0L) + + os.write(f_fd, data) + + job_page_num = 1 + + for fax_file in self.recipient_file_list: + fax_file_name = fax_file[0] + log.debug("Processing file: %s..." % fax_file_name) + + if self.results[fax_file_name] == ERROR_SUCCESS: + fax_file_fd = file(fax_file_name, 'r') + header = fax_file_fd.read(FILE_HEADER_SIZE) + + 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.") + state = STATE_ERROR + break + + 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)) + + for p in range(total_pages): + header = fax_file_fd.read(PAGE_HEADER_SIZE) + + page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, reserved2 = \ + self.decode_page_header(header) + + if page_num == -1: + log.error("Page header error") + state - STATE_ERROR + break + + header = struct.pack(">IIIIII", job_page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, 0L) + os.write(f_fd, header) + + self.write_queue((STATUS_PROCESSING_FILES, job_page_num, '')) + + log.debug("Page=%d PPR=%d RPP=%d BPP=%d Thumb=%s" % + (page_num, ppr, rpp, bytes_to_read, thumbnail_bytes)) + + os.write(f_fd, fax_file_fd.read(bytes_to_read)) + job_page_num += 1 + + fax_file_fd.close() + + if self.check_for_cancel(): + state = STATE_ABORTED + break + + else: + log.error("Skipping file: %s" % fax_file_name) + continue + + os.close(f_fd) + log.debug("Total pages=%d" % self.job_total_pages) + + return state + + + def next_recipient_gen(self): + for a in self.phone_num_list: + yield a + + def next_file_gen(self): + for a in self.recipient_file_list: + yield a + + + def render_file(self, path, title, mime_type, force_single_page=False): + all_pages = True + page_range = '' + page_set = 0 + nup = 1 + + cups.resetOptions() + + if mime_type in ["application/x-cshell", + "application/x-perl", + "application/x-python", + "application/x-shell", + "application/x-sh", + "text/plain",]: + + cups.addOption('prettyprint') + + if nup > 1: + cups.addOption('number-up=%d' % nup) + + if force_single_page: + cups.addOption('page-ranges=1') # Force coverpage to 1 page + + sent_job_id = cups.printFile(self.current_printer, path, title) + cups.resetOptions() + + log.debug("Job ID=%d" % sent_job_id) + job_id = 0 + + time.sleep(1) + + fax_file = '' + complete = False + + end_time = time.time() + 300.0 # wait for 5 min. max + while time.time() < end_time: + log.debug("Waiting for fax...") + + result = list(self.service.CheckForWaitingFax(self.dev.device_uri, prop.username, sent_job_id)) + + fax_file = str(result[7]) + log.debug("Fax file=%s" % fax_file) + + if fax_file: + break + + if self.check_for_cancel(): + log.error("Render canceled. Canceling job #%d..." % sent_job_id) + cups.cancelJob(sent_job_id) + return '', True + + time.sleep(1) + + else: + log.error("Timeout waiting for rendering. Canceling job #%d..." % sent_job_id) + cups.cancelJob(sent_job_id) + return '', False + + return fax_file, False + + + def check_for_cancel(self): + canceled = False + while self.event_queue.qsize(): + try: + event = self.event_queue.get(0) + if event[0] == EVENT_FAX_SEND_CANCELED: + canceled = True + log.debug("Cancel pressed!") + except Queue.Empty: + break + + return canceled + + def render_cover_page(self, a): + log.debug("Creating cover page...") + + pdf = self.cover_func(page_size=coverpages.PAGE_SIZE_LETTER, + total_pages=self.job_total_pages, + + recipient_name=a['name'], + recipient_phone='', # ??? + recipient_fax=a['fax'], + + sender_name=self.sender_name, + sender_phone=user_conf.get('fax', 'voice_phone'), + sender_fax=self.sender_fax, + sender_email=user_conf.get('fax', 'email_address'), + + regarding=self.cover_re, + message=self.cover_message, + preserve_formatting=self.preserve_formatting) + + log.debug("PDF File=%s" % pdf) + fax_file, canceled = self.render_file(pdf, 'Cover Page', "application/pdf", + force_single_page=True) + + try: + os.remove(pdf) + except IOError: + pass + + return fax_file, canceled + + + def write_queue(self, message): + if self.update_queue is not None and message != self.prev_update: + self.update_queue.put(message) + time.sleep(0) + self.prev_update = message + + + def run(self): + pass + + + diff --git a/fax/faxdevice.py b/fax/faxdevice.py new file mode 100644 index 0000000..b832d35 --- /dev/null +++ b/fax/faxdevice.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# +# (c) Copyright 2010 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 +# + +# Local +from base.g import * +from prnt import cups +from base import device, codes +from soapfax import SOAPFaxDevice +from pmlfax import PMLFaxDevice +from marvellfax import MarvellFaxDevice + +def FaxDevice(device_uri=None, printer_name=None, + callback=None, + fax_type=FAX_TYPE_NONE, + disable_dbus=False): + + if fax_type == FAX_TYPE_NONE: + if device_uri is None and printer_name is not None: + printers = cups.getPrinters() + + for p in printers: + if p.name.lower() == printer_name.lower(): + device_uri = p.device_uri + break + else: + raise Error(ERROR_DEVICE_NOT_FOUND) + + if device_uri is not None: + mq = device.queryModelByURI(device_uri) + fax_type = mq['fax-type'] + + log.debug("fax-type=%d" % fax_type) + + if fax_type in (FAX_TYPE_BLACK_SEND_EARLY_OPEN, FAX_TYPE_BLACK_SEND_LATE_OPEN): + return PMLFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_SOAP: + return SOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_LEDMSOAP: + return LEDMSOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_MARVELL: + return MarvellFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + elif fax_type == FAX_TYPE_LEDM: + from ledmfax import LEDMFaxDevice + return LEDMFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus) + + + else: + raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION) diff --git a/fax/filters/pstotiff b/fax/filters/pstotiff new file mode 100755 index 0000000..2b0571e --- /dev/null +++ b/fax/filters/pstotiff @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +import os +import os.path +import time +import sys +import tempfile + +READ_SIZE = 8192 + +total_bytes_read = 0 +temp_in_file = "-" + +if (len(sys.argv) > 6): + temp_in_file = sys.argv[6] + +temp_out_handle, temp_out_fname = tempfile.mkstemp() + +font = "-I/usr/share/cups/fonts" +device = "-sDEVICE=tiffg4 -dMaxStripSize=0 -r204x196 -dNOPAUSE -dBATCH -dSAFER -dPARANOIDSAFER -dSHORTERRORS -dWRITESYSTEMDICT -dGHOSTSCRIPT -sstdout=%stderr -sOutputFile=" + temp_out_fname + " " + temp_in_file + +gs_command = "/usr/bin/gs" + " " + font + " " + device + +exit_code = os.system(gs_command) + +file_len = os.stat(temp_out_fname).st_size +if (file_len < READ_SIZE): + READ_SIZE = file_len + +os.close(temp_out_handle) + +out_handle = open(temp_out_fname, mode='rb') +while (total_bytes_read < file_len): + data = out_handle.read(READ_SIZE) + sys.stdout.write(data) + total_bytes_read += READ_SIZE +out_handle.close() + +os.remove(temp_out_fname) +sys.exit(0) diff --git a/fax/filters/pstotiff.convs b/fax/filters/pstotiff.convs new file mode 100644 index 0000000..83a265c --- /dev/null +++ b/fax/filters/pstotiff.convs @@ -0,0 +1,27 @@ +# Copyright 2010 by HP. +# +######################################################################## +# +# Format of Lines: +# +# source/type destination/type cost filter +# +# General Notes: +# +# The "cost" field is used to find the least costly filters to run +# when converting a job file to a printable format. +# +# All filters *must* accept the standard command-line arguments +# (job-id, user, title, copies, options, [filename or stdin]) to +# work with CUPS. +# + +######################################################################## +# +# TIFF filters... +# + +application/postscript image/tiff 80 pstotiff +application/pdf image/tiff 80 pstotiff +application/vnd.cups-pdf image/tiff 80 pstotiff +application/vnd.cups-postscript image/tiff 80 pstotiff diff --git a/fax/filters/pstotiff.types b/fax/filters/pstotiff.types new file mode 100644 index 0000000..6e2f5be --- /dev/null +++ b/fax/filters/pstotiff.types @@ -0,0 +1,53 @@ +# Copyright 2010-2011 by HP. +# +######################################################################## +# +# Format of Lines: +# +# super/type rules +# +# "rules" can be any combination of: +# +# ( expr ) Parenthesis for expression grouping +# + Logical AND +# , or whitespace Logical OR +# ! Logical NOT +# match("pattern") Pattern match on filename +# extension Pattern match on "*.extension" +# ascii(offset,length) True if bytes are valid printable ASCII +# (CR, NL, TAB, BS, 32-126) +# printable(offset,length) True if bytes are printable 8-bit chars +# (CR, NL, TAB, BS, 32-126, 128-254) +# string(offset,"string") True if bytes are identical to string +# istring(offset,"string") True if bytes are identical to +# case-insensitive string +# char(offset,value) True if byte is identical +# short(offset,value) True if 16-bit integer is identical +# int(offset,value) True if 32-bit integer is identical +# locale("string") True if current locale matches string +# contains(offset,range,"string") True if the range contains the string +# +# General Notes: +# +# MIME type names are case-insensitive. Internally they are converted +# to lowercase. Multiple occurrences of a type will cause the provided +# rules to be appended to the existing definition. Type names are sorted +# in ascending order, so if two types use the same rules to resolve a type +# (e.g. doc extension for two types), the returned type will be the first +# type in the sorted list. +# +# The "printable" rule differs from the "ascii" rule in that it also +# accepts 8-bit characters in the range 128-255. +# +# String constants must be surrounded by "" if they contain whitespace. +# To insert binary data into a string, use the <hex> notation. +# + +######################################################################## +# +# Application-generated files... +# + +#application/vnd.hplip-tiff tiff tif string(0,MM<002A>) string(0,II<2A00>) +image/tiff tiff tif string(0,MM<002A>) string(0,II<2A00>) + diff --git a/fax/ledmfax.py b/fax/ledmfax.py new file mode 100644 index 0000000..7a79d6e --- /dev/null +++ b/fax/ledmfax.py @@ -0,0 +1,691 @@ +# -*- 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: k,shunmugaraj +# Date Created: 10/10/2010 + +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 +import threading +import struct +import time +import xml.parsers.expat as expat +from stat import * +# Local +from base.g import * +from base.codes import * +from base import device, utils, codes, dime, status +from fax import * + + +# **************************************************************************** # + +http_result_pat = re.compile("""HTTP/\d.\d\s(\d+)""", re.I) + +HTTP_OK = 200 +HTTP_ACCEPTED = 202 +HTTP_CREATED = 201 +HTTP_ERROR = 500 + +PIXELS_PER_LINE = 1728 + +# **************************************************************************** # +setPhoneNumXML = """<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--Sample XML file generated by XMLSPY v5 rel. 4 U (http://www.xmlspy.com)--><faxcfgdyn:FaxConfigDyn xmlns:faxcfgdyn=\"http://www.hp.com/schemas/imaging/con/ledm/faxconfigdyn/2009/03/03\" xmlns:dd=\"http://www.hp.com/schemas/imaging/con/dictionaries/1.0/\" xmlns:fax=\"http://www.hp.com/schemas/imaging/con/fax/2008/06/13\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.hp.com/schemas/imaging/con/ledm/faxconfigdyn/2009/03/03 ../schemas/FaxConfigDyn.xsd\"><faxcfgdyn:SystemSettings><dd:PhoneNumber>%s</dd:PhoneNumber></faxcfgdyn:SystemSettings></faxcfgdyn:FaxConfigDyn>""" + +setStationNameXML = """<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--Sample XML file generated by XMLSPY v5 rel. 4 U (http://www.xmlspy.com)--><faxcfgdyn:FaxConfigDyn xmlns:faxcfgdyn=\"http://www.hp.com/schemas/imaging/con/ledm/faxconfigdyn/2009/03/03\" xmlns:dd=\"http://www.hp.com/schemas/imaging/con/dictionaries/1.0/\" xmlns:fax=\"http://www.hp.com/schemas/imaging/con/fax/2008/06/13\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.hp.com/schemas/imaging/con/ledm/faxconfigdyn/2009/03/03 ../schemas/FaxConfigDyn.xsd\"><faxcfgdyn:SystemSettings><dd:CompanyName>%s</dd:CompanyName></faxcfgdyn:SystemSettings></faxcfgdyn:FaxConfigDyn>""" + +createJobXML = """<?xml version=\"1.0\" encoding=\"UTF-8\"?><!--THIS DATA SUBJECT TO DISCLAIMER(S)INCLUDED WITH THE PRODUCT OF ORIGIN.--><fpsdyn:FaxPCSendDyn xmlns:fpsdyn=\"http://www.hp.com/schemas/imaging/con/ledm/printtofaxdyn/2008/11/24\" xmlns:dd=\"http://www.hp.com/schemas/imaging/con/dictionaries/1.0/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.hp.com/schemas/imaging/con/ledm/printtofaxdyn/2008/11/24 ../schemas/FaxPCSendDyn.xsd\"><dd:Version><dd:Revision>1.0</dd:Revision></dd:Version><fpsdyn:FaxPCSendConfig><fpsdyn:FaxTxPhoneNumber>%s</fpsdyn:FaxTxPhoneNumber><fpsdyn:NumPages>%d</fpsdyn:NumPages><fpsdyn:TTI_Control>TTI_Off</fpsdyn:TTI_Control></fpsdyn:FaxPCSendConfig></fpsdyn:FaxPCSendDyn>""" + +pageConfigXML = """<?xml version=\"1.0\" encoding=\"UTF-8\" ?><!-- THIS DATA SUBJECT TO DISCLAIMER(S)INCLUDED WITH THE PRODUCT OF ORIGIN.--><fpsdyn:FaxPCSendDyn xmlns:fpsdyn=\"http://www.hp.com/schemas/imaging/con/ledm/printtofaxdyn/2008/11/24\" xmlns:dd=\"http://www.hp.com/schemas/imaging/con/dictionaries/1.0/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.hp.com/schemas/imaging/con/ledm/printtofaxdyn/2008/11/24 ../schemas/FaxPCSendDyn.xsd\"><dd:Version><dd:Revision>1.0</dd:Revision></dd:Version><fpsdyn:PageConfig><fpsdyn:PageNum>%d</fpsdyn:PageNum><fpsdyn:Width>1728</fpsdyn:Width><fpsdyn:Height>2200</fpsdyn:Height><fpsdyn:ImageType>BW</fpsdyn:ImageType><fpsdyn:Compression>mh</fpsdyn:Compression><fpsdyn:HorizontalDPI>%d</fpsdyn:HorizontalDPI><fpsdyn:VerticalDPI>%d</fpsdyn:VerticalDPI></fpsdyn:PageConfig></fpsdyn:FaxPCSendDyn>""" + +cancelJobXML = """<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- THIS DATA SUBJECT TO DISCLAIMER(S) INCLUDED WITH THE PRODUCT OF ORIGIN.--><j:Job xmlns:j=\"http://www.hp.com/schemas/imaging/con/ledm/jobs/2009/04/30\" xmlns:dd=\"http://www.hp.com/schemas/imaging/con/dictionaries/1.0/\" xmlns:fax=\"http://www.hp.com/schemas/imaging/con/fax/2008/06/13\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.hp.com/schemas/imaging/con/ledm/jobs/2009/04/30 ../schemas/Jobs.xsd\"><j:JobUrl>%s</j:JobUrl><j:JobState>Canceled</j:JobState></j:Job>""" + +# **************************************************************************** # +class LEDMFaxDevice(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 put(self, url, post): + data = """PUT %s HTTP/1.1\r +Connection: Keep-alive\r +User-agent: hplip/2.0\r +Host: %s\r +Content-length: %d\r +\r +%s""" % (url, self.http_host, len(post), post) + log.log_data(data) + self.writeLEDM(data) + response = cStringIO.StringIO() + + while self.readLEDM(512, response, timeout=5): + pass + + response = response.getvalue() + log.log_data(response) + self.closeLEDM() + + match = http_result_pat.match(response) + if match is None: return HTTP_OK + try: + code = int(match.group(1)) + except (ValueError, TypeError): + code = HTTP_ERROR + + return code == HTTP_OK + + + def setPhoneNum(self, num): + xml = setPhoneNumXML %(num) + log.debug("SetPhoneNum:xml Value:%s" %xml) + return self.put("/DevMgmt/FaxConfigDyn.xml", xml) + + + def getPhoneNum(self): + return self.readAttributeFromXml("/DevMgmt/FaxConfigDyn.xml",'faxcfgdyn:faxconfigdyn-faxcfgdyn:systemsettings-dd:phonenumber') + + phone_num = property(getPhoneNum, setPhoneNum) + + + def setStationName(self, name): + xml = setStationNameXML %(name) + return self.put("/DevMgmt/FaxConfigDyn.xml", xml) + + + def getStationName(self): + return self.readAttributeFromXml("/DevMgmt/FaxConfigDyn.xml",'faxcfgdyn:faxconfigdyn-faxcfgdyn:systemsettings-dd:companyname') + + station_name = property(getStationName, setStationName) + + 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 = LEDMFaxSendThread(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 LEDMFaxSendThread(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) + + if dev.bus == 'net': + self.http_host = "%s:8080" % self.dev.host + else: + self.http_host = 'localhost:8080' + + + def run(self): + + 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 + error_state = STATUS_ERROR + 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((error_state, 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("LEDM 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)) + + faxnum = recipient['fax'].encode('ascii') + + createJob = createJobXML %(faxnum, total_pages) + data = self.format_http_post("/FaxPCSend/Job",len(createJob),createJob) + log.log_data(data) + + self.dev.openLEDM() + self.dev.writeLEDM(data) + response = cStringIO.StringIO() + try: + while self.dev.readLEDM(512, response, timeout=5): + pass + except Error: + fax_send_state = FAX_SEND_STATE_ERROR + self.dev.closeLEDM() + break + self.dev.closeLEDM() + + response = response.getvalue() + log.log_data(response) + if self.get_error_code(response) == HTTP_CREATED: + fax_send_state = FAX_SEND_STATE_DOWNLOADPAGES + else: + fax_send_state = FAX_SEND_STATE_ERROR + log.error("Create Job request failed") + break + + responsestr = str(response) + pos = responsestr.find("/Jobs/JobList/",0,len(responsestr)) + pos1 = responsestr.find("Content-Length",0,len(responsestr)) + jobListURI = responsestr[pos:pos1].strip() + log.debug("jobListURI = [%s]" %(jobListURI)) + + elif fax_send_state == FAX_SEND_STATE_DOWNLOADPAGES: # -------------- DownloadPages (110, 60, 0) + log.debug("%s State: DownloadPages" % ("*"*20)) + page = StringIO() + log.debug("Total Number of pages are:%d" %total_pages) + 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 + + pageConfigURI = self.dev.readAttributeFromXml(jobListURI,"j:job-faxpcsendstatus-resourceuri") + log.debug("pageConfigURI:[%s]" %pageConfigURI) + + pageConfig = pageConfigXML %(page_num,hort_dpi,vert_dpi) + xmldata = self.format_http_post(pageConfigURI,len(pageConfig),pageConfig) + log.log_data(xmldata) + + self.dev.openLEDM() + try: + self.dev.writeLEDM(xmldata) + except Error: + fax_send_state = FAX_SEND_STATE_ERROR + self.dev.closeLEDM() + break + + response = cStringIO.StringIO() + try: + while self.dev.readLEDM(512, response, timeout=5): + pass + except Error: + fax_send_state = FAX_SEND_STATE_ERROR + self.dev.closeLEDM() + break + + self.dev.closeLEDM() + response = (response.getvalue()) + log.log_data(response) + if self.get_error_code(response) != HTTP_ACCEPTED: + fax_send_state = FAX_SEND_STATE_ERROR + log.error("Page config data is not accepted by the device") + break + + pageImageURI = self.dev.readAttributeFromXml(jobListURI,"j:job-faxpcsendstatus-resourceuri") + while(True): + if self.check_for_cancel(): + fax_send_state = FAX_SEND_STATE_ABORT + break + + Status, Fax_State = self.checkForError(jobListURI) + if Status == FAX_SEND_STATE_ERROR and (Fax_State == STATUS_ERROR_IN_TRANSMITTING or + Fax_State == STATUS_ERROR_IN_CONNECTING or Fax_State == STATUS_ERROR_PROBLEM_IN_FAXLINE or + Fax_State == STATUS_JOB_CANCEL): + log.debug("setting state to FAX_SEND_STATE_ERROR") + fax_send_state = FAX_SEND_STATE_ERROR + error_state = Fax_State + break + elif Status == FAX_SEND_STATE_SUCCESS: + break + + if fax_send_state == FAX_SEND_STATE_ABORT or fax_send_state == FAX_SEND_STATE_ERROR: + break + + + xmldata = self.format_http_post(pageImageURI,len(data),"","application/octet-stream") + log.debug("Sending Page Image XML Data [%s] to the device" %str(xmldata)) + self.dev.openLEDM() + self.dev.writeLEDM(xmldata) + log.debug("Sending Raw Data to printer............") + try: + self.dev.writeLEDM(data) + except Error: + fax_send_state = FAX_SEND_STATE_ERROR + self.dev.closeLEDM() + break + + response = cStringIO.StringIO() + try: + while self.dev.readLEDM(512, response, timeout=10): + pass + except Error: + fax_send_state = FAX_SEND_STATE_ERROR + self.dev.closeLEDM() + break + + self.dev.closeLEDM() + response = response.getvalue() + log.log_data(response) + + if self.get_error_code(response) != HTTP_ACCEPTED: + log.error("Image Data is not accepted by the device") + 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) + fax_send_state = FAX_SEND_STATE_SUCCESS + + + elif fax_send_state == FAX_SEND_STATE_CANCELJOB: # -------------- CancelJob (110, 80, 0) + log.debug("%s State: CancelJob" % ("*"*20)) + + xmldata = cancelJobXML %(jobListURI) + data = self.format_http_put(jobListURI,len(xmldata),xmldata) + log.log_data(data) + + self.dev.openLEDM() + self.dev.writeLEDM(data) + + response = cStringIO.StringIO() + try: + while self.dev.readLEDM(512, response, timeout=10): + pass + except Error: + fax_send_state = FAX_SEND_STATE_ERROR + self.dev.closeLEDM() + break + self.dev.closeLEDM() + response = response.getvalue() + log.log_data(response) + + if self.get_error_code(response) == HTTP_OK: + fax_send_state = FAX_SEND_STATE_CLOSE_SESSION + else: + fax_send_state = FAX_SEND_STATE_ERROR + log.error("Job Cancel Request Failed") + + + 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: + ff.close() + except NameError: + pass + + #time.sleep(1) + + self.dev.closeLEDM() + 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 checkForError(self,uri): + stream = cStringIO.StringIO() + data = self.dev.FetchLEDMUrl(uri) + if not data: + log.error("Unable To read the XML data from device") + return "" + + xmlDict = utils.XMLToDictParser().parseXML(data) + log.debug("Read Attribute:%s and it is value:%s" %(uri,data)) + + FAX_SEND_STATE_ERROR = 20 + FAX_SEND_STATE_SUCCESS = 30 + state = FAX_SEND_STATE_ERROR + Fax_send_state = STATUS_ERROR + + if cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxmachinestatus']),"Transmitting")==0 \ + and cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxerrorstatus']),"CommunicationError")== 0: + state = FAX_SEND_STATE_ERROR + Fax_send_state = STATUS_ERROR_IN_TRANSMITTING + elif(cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxmachinestatus']),"Connecting")==0 \ + and cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxerrorstatus']),"NoAnswer")== 0): + state = FAX_SEND_STATE_ERROR + Fax_send_state = STATUS_ERROR_IN_CONNECTING + elif(cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxerrorstatus']),"PcDisconnect")==0 \ + and cmp(str(xmlDict['j:job-faxpcsendstatus-pagestatus-state']),"Error")== 0): + state = FAX_SEND_STATE_ERROR + Fax_send_state = STATUS_ERROR_PROBLEM_IN_FAXLINE + elif(cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxerrorstatus']),"Stop")==0 \ + and cmp(str(xmlDict['j:job-faxpcsendstatus-pagestatus-state']),"Error")== 0): + state = FAX_SEND_STATE_ERROR + Fax_send_state = STATUS_JOB_CANCEL + elif(cmp(str(xmlDict['j:job-faxpcsendstatus-faxtxmachinestatus']),"Transmitting")== 0): + state = FAX_SEND_STATE_SUCCESS + Fax_send_state = FAX_SEND_STATE_SUCCESS + return state,Fax_send_state + + def format_http_post(self, requst, ledmlen, xmldata, content_type="text/xml; charset=utf-8"): + host = self.http_host + + return utils.cat( +"""POST $requst HTTP/1.1\r +Host: $host\r +User-Agent: hplip/2.0\r +Content-Type: $content_type\r +Content-Length: $ledmlen\r +Connection: Keep-alive\r +SOAPAction: ""\r +\r +$xmldata""") + + def format_http_put(self, requst, ledmlen, xmldata, content_type="text/xml; charset=utf-8"): + host = self.http_host + return utils.cat( +"""PUT $requst HTTP/1.1\r +Host: $host\r +User-Agent: hplip/2.0\r +Content-Type: $content_type\r +Content-Length: $ledmlen\r +\r +$xmldata""") + + + + + + + + diff --git a/fax/ledmsoapfax.py b/fax/ledmsoapfax.py new file mode 100644 index 0000000..b58f0c1 --- /dev/null +++ b/fax/ledmsoapfax.py @@ -0,0 +1,106 @@ +# -*- 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 * +from ledmfax import * +from soapfax import SOAPFaxSendThread +from soapfax import SOAPFaxDevice + + +# **************************************************************************** # +class LEDMSOAPFaxDevice(SOAPFaxDevice): + + + def __init__(self, device_uri=None, printer_name=None, + callback=None, + fax_type=FAX_TYPE_NONE, + disable_dbus=False): + + SOAPFaxDevice.__init__(self, device_uri, + printer_name, + callback, fax_type, + disable_dbus) + + #LEDM Specific functions + def put(self, url, post): + data = """PUT %s HTTP/1.1\r +Connection: Keep-alive\r +User-agent: hplip/2.0\r +Host: %s\r +Content-length: %d\r +\r +%s""" % (url, self.http_host, len(post), post) + log.log_data(data) + self.writeEWS_LEDM(data) + response = cStringIO.StringIO() + + while self.readEWS_LEDM(4096, response, timeout=5): + pass + + response = response.getvalue() + log.log_data(response) + self.closeEWS_LEDM() + + match = http_result_pat.match(response) + if match is None: return HTTP_OK + try: + code = int(match.group(1)) + except (ValueError, TypeError): + code = HTTP_ERROR + + return code == HTTP_OK + + + def setPhoneNum(self, num): + xml = setPhoneNumXML %(num) + log.debug("SetPhoneNum:xml Value:%s" %xml) + return self.put("/DevMgmt/FaxConfigDyn.xml", xml) + + + def getPhoneNum(self): + return self.readAttributeFromXml_EWS("/DevMgmt/FaxConfigDyn.xml",'faxcfgdyn:faxconfigdyn-faxcfgdyn:systemsettings-dd:phonenumber') + + phone_num = property(getPhoneNum, setPhoneNum) + + + def setStationName(self, name): + xml = setStationNameXML %(name) + return self.put("/DevMgmt/FaxConfigDyn.xml", xml) + + + def getStationName(self): + return self.readAttributeFromXml_EWS("/DevMgmt/FaxConfigDyn.xml",'faxcfgdyn:faxconfigdyn-faxcfgdyn:systemsettings-dd:companyname') + + station_name = property(getStationName, setStationName) diff --git a/fax/marvellfax.py b/fax/marvellfax.py new file mode 100644 index 0000000..4d5ced3 --- /dev/null +++ b/fax/marvellfax.py @@ -0,0 +1,872 @@ +# -*- coding: utf-8 -*- +# +# (c) Copyright 2010 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: Suma Byrappa +# + +# Std Lib +import sys +import os +import os.path +import struct +import time +import threading +import cStringIO + +from stat import * + +# Local +from base.g import * +from base.codes import * +from base import device, utils, pml, codes +from prnt import cups +from fax import * +import hpmudext + +try: + from ctypes import cdll + from ctypes import * + import ctypes.util as cu +except ImportError: + log.error("Marvell fax support requires python-ctypes module. Exiting!") + sys.exit(1) + + +# **************************************************************************** # +# Marvell Message Types +START_FAX_JOB = 0 +END_FAX_JOB = 1 +SEND_FAX_JOB = 2 +GET_FAX_LOG_ENTRY = 5 +GET_FAX_SETTINGS = 9 +SET_FAX_SETTINGS = 10 +CLEAR_FAX_STATUS = 11 +REQUEST_FAX_STATUS = 12 +FAX_DATA_BLOCK = 13 + +SUCCESS = 0 +FAILURE = 1 + +FAX_DATA_BLOCK_SIZE = 4096 + +# Fax data variant header TTI header control +TTI_NONE = 0 +TTI_PREPENDED_TO_IMAGE = 1 +TTI_OVERLAYED_ON_IMAGE = 2 + +# **************************************************************************** # +class MarvellFaxDevice(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 + + try: + sendfax_path = utils.which('hp-sendfax') + sendfax_a_path = os.readlink(sendfax_path+"/hp-sendfax") + if not os.path.isabs(sendfax_a_path): + sendfax_f_path = os.path.join(sendfax_path, sendfax_a_path) + else: + sendfax_f_path = sendfax_a_path + + sendfax_abs_path = os.path.realpath(sendfax_f_path) + (head, tail) = os.path.split(sendfax_abs_path) + + lib_name = head+"/fax/plugins/fax_marvell.so" + log.debug("Load the library %s\n" % lib_name) + if not os.path.exists(lib_name): + log.error("Loading %s failed. Try after installing plugin libraries\n" %lib_name); + log.info("Run \"hp-plugin\" to installa plugin libraries if you are not automatically prompted\n") + job_id =0; + self.service.SendEvent(device_uri, printer_name, EVENT_FAX_FAILED_MISSING_PLUGIN, os.getenv('USER'), job_id, "Plugin is not installed") + sys.exit(1) + else: + self.libfax_marvell = cdll.LoadLibrary(lib_name) + except Error, e: + log.error("Loading fax_marvell failed (%s)\n" % e.msg); + sys.exit(1) + + + # Creates a message packet for message type given in argument, and sends it to device + # + # 1. Gets the message packet using fax_marvell.so + # 2. Writes the packets to device + # 3. Returns the result of send operation + def send_packet_for_message(self, msg_type, param1=0, param2=0, status=0, data_len=0): + int_array_8 = c_int * 8 + i_buf = int_array_8(0, 0, 0, 0, 0, 0, 0, 0) + + result = self.libfax_marvell.create_packet(msg_type, param1, param2, status, data_len, byref(i_buf)) + buf = buffer(i_buf) + log.log_data(buf, 32) + self.writeMarvellFax(buf) +# self.closeMarvellFax() + + return result + + + # Reads response message packet from the device for message type given in argument. + # Reads the response from device, and sends the data read to the caller of this method + # No Marvell specific code or info + def read_response_for_message(self, msg_type): + ret_buf = cStringIO.StringIO() + while self.readMarvellFax(32, ret_buf, timeout=10): + pass + + ret_buf = ret_buf.getvalue() + #self.closeMarvellFax() + + log.debug("response_for_message (%d): response packet is\n" % msg_type) + log.log_data(ret_buf, 32) + + return ret_buf + + + def setPhoneNum(self, num): + log.debug("************************* setPhoneNum (%s) START **************************" % num) + + set_buf = cStringIO.StringIO() + + int_array = c_int * 8 + i_buf = int_array(0, 0, 0, 0, 0, 0, 0, 0) + + char_array = c_char * 308 + c_buf = char_array() + + date_array = c_char * 15 + date_buf = date_array() + t = time.localtime() + date_buf = "%4d%02d%02d%02d%02d%02d" % (t[0], t[1], t[2], t[3], t[4], t[5]) + log.debug("Date and Time string is ==>") + log.debug(date_buf) + + result = self.libfax_marvell.create_packet(SET_FAX_SETTINGS, 0, 0, 0, 0, byref(i_buf)) + result = self.libfax_marvell.create_fax_settings_packet(self.station_name, str(num), date_buf, byref(c_buf)) + + msg_buf = buffer(i_buf) + msg_c_buf = buffer(c_buf) + + for i in range(0, 32): + set_buf.write(msg_buf[i]) + for i in range(0, 308): + set_buf.write(msg_c_buf[i]) + + set_buf = set_buf.getvalue() + log.debug("setPhoneNum: send SET_FAX_SETTINGS message and data ===> ") + log.log_data(set_buf, 340) + + self.writeMarvellFax(set_buf) + ret_buf = cStringIO.StringIO() + while self.readMarvellFax(32, ret_buf, timeout=10): + pass + ret_buf = ret_buf.getvalue() + self.closeMarvellFax() + + response = self.libfax_marvell.extract_response(ret_buf) + log.debug("setPhoneNum: response is %d" % response) + + log.debug("************************* setPhoneNum END **************************") + return response + + + def getPhoneNum(self): + int_array_8 = c_int * 8 + i_buf = int_array_8(0, 0, 0, 0, 0, 0, 0, 0) + ph_buf = int_array_8(0, 0, 0, 0, 0, 0, 0, 0) + + log.debug("******************** getPhoneNum START **********************") + + result = self.libfax_marvell.create_packet(GET_FAX_SETTINGS, 0, 0, 0, 0, byref(i_buf)) + + buf = buffer(i_buf) + self.writeMarvellFax(buf) + #self.closeMarvellFax() + ret_buf = cStringIO.StringIO() + while self.readMarvellFax(512, ret_buf, timeout=10): + pass + ret_buf = ret_buf.getvalue() + self.closeMarvellFax() + + response = self.libfax_marvell.extract_response(ret_buf) + log.debug("create_packet: response is %d" % response) + + response = self.libfax_marvell.extract_phone_number(ret_buf, ph_buf) + ph_num_buf = cStringIO.StringIO() + for i in range(0, 7): + if ph_buf[i]: + ph_num_buf.write(str(ph_buf[i])) + + ph_num_buf = ph_num_buf.getvalue() + log.debug("getPhoneNum: ph_num_buf=%s " % (ph_num_buf)) + + log.debug("******************** getPhoneNum END **********************") + return ph_num_buf + + + # Note down the fax (phone) number + phone_num = property(getPhoneNum, setPhoneNum) + + + # Set the station name in the device's settings + # + def setStationName(self, name): + log.debug("************************* setStationName(%s) START **************************" % name) + + int_array = c_int * 8 + i_buf = int_array(0, 0, 0, 0, 0, 0, 0, 0) + set_buf = cStringIO.StringIO() + + char_array = c_char * 308 + c_buf = char_array() + + date_array = c_char * 15 + date_buf = date_array() + t = time.localtime() + date_buf = "%4d%02d%02d%02d%02d%02d" % (t[0], t[1], t[2], t[3], t[4], t[5]) + log.debug("Date and Time string is ==>") + log.debug(date_buf) + + result = self.libfax_marvell.create_packet(SET_FAX_SETTINGS, 0, 0, 0, 0, byref(i_buf)) + result = self.libfax_marvell.create_fax_settings_packet(str(name), self.phone_num, date_buf, byref(c_buf)) + + msg_buf = buffer(i_buf) + msg_c_buf = buffer(c_buf) + + for i in range(0, 32): + set_buf.write(msg_buf[i]) + for i in range(0, 308): + set_buf.write(msg_c_buf[i]) + set_buf = set_buf.getvalue() + log.debug("setStationName: SET_FAX_SETTINGS message and data ===> ") + log.log_data(set_buf, 340) + + self.writeMarvellFax(set_buf) + ret_buf = cStringIO.StringIO() + while self.readMarvellFax(32, ret_buf, timeout=10): + pass + ret_buf = ret_buf.getvalue() + self.closeMarvellFax() + + response = self.libfax_marvell.extract_response(ret_buf) + log.debug("setStationName: response is %d" % response) + + log.debug("************************* setStationName END **************************") + return response + + + def getStationName(self): + int_array = c_int * 8 + i_buf = int_array(0, 0, 0, 0, 0, 0, 0, 0) + st_buf = create_string_buffer(128) + + log.debug("************************* getStationName START **************************") + + result = self.libfax_marvell.create_packet(GET_FAX_SETTINGS, 0, 0, 0, 0, byref(i_buf)) + + buf = buffer(i_buf) + self.writeMarvellFax(buf) + #self.closeMarvellFax() + + ret_buf = cStringIO.StringIO() + while self.readMarvellFax(512, ret_buf, timeout=10): + pass + + ret_buf = ret_buf.getvalue() + self.closeMarvellFax() + + response = self.libfax_marvell.extract_response(ret_buf) + log.debug("getStationName: response is %d" % response) + + result = self.libfax_marvell.extract_station_name(ret_buf, st_buf) + log.debug("getStationName: station_name=%s ; result is %d" % (st_buf.value, result)) + + log.debug("************************* getStationName END **************************") + return st_buf.value + + + # Note down the station-name + station_name = property(getStationName, setStationName) + + + # Set date and time in the device's settings + # + # 1. Gets the message packet and fax_settings packet using fax_marvell.so + # 2. Writes the packets to the device; Reads response from the device + # 3. Extracts the status from the device's response + def setDateAndTime(self): + int_array = c_int * 8 + i_buf = int_array(0, 0, 0, 0, 0, 0, 0, 0) + + log.debug("************************* setDateAndTime START **************************") + + c_buf = create_string_buffer(308) + set_buf = cStringIO.StringIO() + ret_buf = cStringIO.StringIO() + date_array = c_char * 15 + date_buf = date_array() + + t = time.localtime() + + date_buf = "%4d%02d%02d%02d%02d%02d" % (t[0], t[1], t[2], t[3], t[4], t[5]) + log.debug("Date and Time string is ==>") + log.debug(date_buf) + + result = self.libfax_marvell.create_packet(SET_FAX_SETTINGS, 0, 0, 0, 0, byref(i_buf)) + result = create_marvell_faxsettings_pkt(self.phone_num, self.station_name, date_buf, c_buf) + + msg_buf = buffer(i_buf) + for i in range(0, 31): + set_buf.write(msg_buf[i]) + + set_buf.write(c_buf.raw) + set_buf = set_buf.getvalue() + self.dev.writeMarvellFax(set_buf) + while self.dev.readMarvellFax(32, ret_buf, timeout=5): + pass + ret_buf = ret_buf.getvalue() + self.closeMarvellFax() + + response = self.libfax_marvell.extract_response(ret_buf) + log.debug("setDateAndTime: response is %d" % response) + + return response + + + # Get the state of the device + # + # 1. Gets the message packet using fax_marvell.so + # 2. Writes the packet to the device; Reads response from the device + # 3. Extracts the response status and device status from the device's response + def getFaxDeviceState(self): + log.debug("************************* getFaxDeviceState: START **************************") + + int_array = c_int * 8 + i_buf = int_array(0, 0, 0, 0, 0, 0, 0, 0) + param1 = c_int(0) + + result = self.libfax_marvell.create_packet(REQUEST_FAX_STATUS, 0, 0, 0, 0, byref(i_buf)) + buf = buffer(i_buf) + self.writeMarvellFax(buf) + + ret_buf = cStringIO.StringIO() + while self.readMarvellFax(32, ret_buf, timeout=5): + pass + ret_buf = ret_buf.getvalue() + self.closeMarvellFax() + + response = self.libfax_marvell.extract_response(ret_buf) + log.debug("getFaxDeviceState: response is %d" % response) + + return response + + + # Creates a thread which does actual Fax submission the state of the device + # + 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 = MarvellFaxSendThread(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 + + + +# **************************************************************************** # +# Does the actual Fax transmission +# **************************************************************************** # +class MarvellFaxSendThread(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) + + + def run(self): + + 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() + + rec_name = None + rec_num = None + + state = STATE_READ_SENDER_INFO + self.rendered_file_list = [] + + while state != STATE_DONE: # --------------------------------- Fax state machine + if self.check_for_cancel(): + log.debug("***** Job is Cancelled.") + state = STATE_ABORTED + + log.debug("*************** STATE=(%d, 0, 0)" % state) + + if state == STATE_ABORTED: # --------------------------------- Aborted + log.error("Aborted by user.") + self.write_queue((STATUS_IDLE, 0, '')) + state = STATE_CLEANUP + + + elif state == STATE_SUCCESS: # --------------------------------- Success + log.debug("Success.") + self.write_queue((STATUS_COMPLETED, 0, '')) + state = STATE_CLEANUP + + + elif state == STATE_ERROR: # --------------------------------- Error + log.error("Error, aborting.") + self.write_queue((STATUS_ERROR, 0, '')) + state = STATE_CLEANUP + + + elif state == STATE_BUSY: # --------------------------------- Busy + log.error("Device busy, aborting.") + self.write_queue((STATUS_BUSY, 0, '')) + state = STATE_CLEANUP + + + elif state == STATE_READ_SENDER_INFO: # --------------------------------- Get sender info + 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 + self.sender_fax = self.dev.phone_num + except Error: + log.error("Getting station-name and phone_num failed!") + state = STATE_ERROR + + finally: + self.dev.close() + + + elif state == STATE_PRERENDER: # --------------------------------- Pre-render non-G3 files + log.debug("%s State: Pre-render non-G3 files" % ("*"*20)) + state = self.pre_render(STATE_COUNT_PAGES) + + + elif state == STATE_COUNT_PAGES: # --------------------------------- Get total page count + 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 + log.debug("%s State: Next recipient" % ("*"*20)) + state = STATE_COVER_PAGE + + try: + recipient = next_recipient.next() + + self.write_queue((STATUS_SENDING_TO_RECIPIENT, 0, recipient['name'])) + + rec_name = recipient['name'] + rec_num = recipient['fax'].encode('ascii') + log.debug("recipient is %s num is %s" % (rec_name, rec_num)) + + except StopIteration: + state = STATE_SUCCESS + log.debug("Last recipient.") + continue + + self.recipient_file_list = self.rendered_file_list[:] + + + elif state == STATE_COVER_PAGE: # --------------------------------- Create cover page + 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) + log.debug("%s State: Handle single file" % ("*"*20)) + state = self.single_file(STATE_SEND_FAX) + + elif state == STATE_MERGE_FILES: # --------------------------------- Merge multiple G3 files + log.debug("%s State: Merge multiple files" % ("*"*20)) + log.debug("Not merging the files for Marvell support") + state = STATE_SEND_FAX + + elif state == STATE_SEND_FAX: # --------------------------------- Send fax state machine + log.debug("%s State: Send fax" % ("*"*20)) + state = STATE_NEXT_RECIPIENT + + next_file = self.next_file_gen() + + FAX_SEND_STATE_DONE = 0 + FAX_SEND_STATE_SUCCESS = 10 + FAX_SEND_STATE_ABORT = 21 + FAX_SEND_STATE_ERROR = 22 + FAX_SEND_STATE_BUSY = 25 + FAX_SEND_STATE_DEVICE_OPEN = 30 + FAX_SEND_STATE_NEXT_FILE = 35 + FAX_SEND_STATE_CHECK_IDLE = 40 + FAX_SEND_STATE_START_JOB_REQUEST = 50 + FAX_SEND_STATE_SEND_JOB_REQUEST = 60 + FAX_SEND_STATE_SET_PARAMS = 70 + FAX_SEND_STATE_SEND_FAX_HEADER = 80 + FAX_SEND_STATE_SEND_FILE_DATA = 90 + FAX_SEND_STATE_END_FILE_DATA = 100 + FAX_SEND_STATE_END_JOB_REQUEST = 110 + FAX_SEND_STATE_GET_LOG_INFORMATION = 120 + + monitor_state = False + current_state = SUCCESS + 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.getFaxDeviceState() + if fax_state != SUCCESS: + log.error("Device is in error state=%d" % fax_state) + fax_send_state = FAX_SEND_STATE_ERROR + state = STATE_ERROR + + + log.debug("********* FAX_SEND_STATE=(%d, %d, %d)" % (STATE_SEND_FAX, fax_send_state, current_state)) + + if fax_send_state == FAX_SEND_STATE_ABORT: # -------------- Abort + monitor_state = False + fax_send_state = FAX_SEND_STATE_END_JOB_REQUEST + state = STATE_ABORTED + + elif fax_send_state == FAX_SEND_STATE_ERROR: # -------------- Error + log.error("Fax send error.") + monitor_state = False + + fax_send_state = FAX_SEND_STATE_END_JOB_REQUEST + state = STATE_ERROR + + elif fax_send_state == FAX_SEND_STATE_BUSY: # -------------- Busy + log.error("Fax device busy.") + monitor_state = False + fax_send_state = FAX_SEND_STATE_END_JOB_REQUEST + state = STATE_BUSY + + elif fax_send_state == FAX_SEND_STATE_SUCCESS: # -------------- Success + log.debug("Fax send success.") + monitor_state = False + fax_send_state = FAX_SEND_STATE_END_JOB_REQUEST + state = STATE_NEXT_RECIPIENT + + elif fax_send_state == FAX_SEND_STATE_DEVICE_OPEN: # -------------- Device open + log.debug("%s State: Open device" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_NEXT_FILE + 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_NEXT_FILE: # -------------- Device open + log.debug("%s State: Open device" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_CHECK_IDLE + try: + fax_file = next_file.next() + self.f = fax_file[0] + log.debug("***** file name is : %s..." % self.f) + except StopIteration: + log.debug("file(s) are sent to the device" ) + fax_send_state = FAX_SEND_STATE_DONE + + + elif fax_send_state == FAX_SEND_STATE_CHECK_IDLE: # -------------- Check for initial idle + log.debug("%s State: Check idle" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_START_JOB_REQUEST + + 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 Version=%d Total Pages=%d hDPI=%d vDPI=%d Size=%d Resolution=%d Encoding=%d" + % (magic, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding)) + + dev_state = self.dev.getFaxDeviceState() + + if (dev_state == 0): + log.debug("State: device status is zero ") + else: + log.debug("State: device status is non-zero ") + fax_send_state = FAX_SEND_STATE_BUSY + + + elif fax_send_state == FAX_SEND_STATE_START_JOB_REQUEST: # -------------- Request fax start + log.debug("%s State: Request start" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SEND_JOB_REQUEST + + file_len = os.stat(self.f)[ST_SIZE] + tx_data_len = file_len - FILE_HEADER_SIZE - (PAGE_HEADER_SIZE*total_pages) + log.debug("#### file_len = %d" % file_len) + log.debug("#### tx_data_len = %d" % tx_data_len) + ret_value = self.dev.send_packet_for_message(START_FAX_JOB, tx_data_len, 0, 0, 0) + if ret_value: + log.debug("Sending start fax request failed with %d" % ret_value) + fax_send_state = FAX_SEND_STATE_ERROR + else: + log.debug("Successfully sent start fax request") + ret_buf = self.dev.read_response_for_message(START_FAX_JOB) + dev_response = self.dev.libfax_marvell.extract_response(ret_buf) + if dev_response: + log.debug("start-fax request failed with %d" % dev_response) + fax_send_state = FAX_SEND_STATE_ERROR + else: + log.debug("start-fax request is successful") + + elif fax_send_state == FAX_SEND_STATE_SEND_JOB_REQUEST: # -------------- Set data request + log.debug("%s State: Send data request" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SET_PARAMS + + ret_value = self.dev.send_packet_for_message(SEND_FAX_JOB) + if ret_value: + log.debug("Sending send-data request failed with %d" % ret_value) + fax_send_state = FAX_SEND_STATE_ERROR + else: + log.debug("Successfully sent send-fax request") + + + elif fax_send_state == FAX_SEND_STATE_SET_PARAMS: # -------------- Set fax send params + log.debug("%s State: Set params" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SEND_FAX_HEADER + + c_buf = create_string_buffer(68) + set_buf = cStringIO.StringIO() + + no_data = None + ret_val = self.dev.libfax_marvell.create_job_settings_packet(no_data, rec_num, c_buf) + set_buf.write(c_buf.raw) + set_buf = set_buf.getvalue() + + self.dev.writeMarvellFax(set_buf) + #self.dev.closeMarvellFax() + + + elif fax_send_state == FAX_SEND_STATE_SEND_FAX_HEADER: # -------------- Fax header + # Taken care by the device + fax_send_state = FAX_SEND_STATE_SEND_FILE_DATA + + elif fax_send_state == FAX_SEND_STATE_SEND_FILE_DATA: # --------------------------------- Send fax pages state machine + log.debug("%s State: Send pages" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_END_FILE_DATA + current_state = SUCCESS + page = StringIO() + + file_len = os.stat(self.f)[ST_SIZE] + bytes_to_read = file_len - FILE_HEADER_SIZE - (PAGE_HEADER_SIZE*total_pages) + + for p in range(total_pages): + + if self.check_for_cancel(): + current_state = FAILURE + + if current_state == FAILURE: + break + + try: + header = ff.read(PAGE_HEADER_SIZE) + except IOError: + log.error("Unable to read fax file.") + current_state = FAILURE + continue + + page_num, ppr, rpp, b_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, b_to_read, thumbnail_bytes)) + + page.write(ff.read(b_to_read)) + thumbnail = ff.read(thumbnail_bytes) # thrown away for now (should be 0 read) + page.seek(0) + bytes_to_write = b_to_read + total_read = 0 + while (bytes_to_write > 0): + try: + data = page.read(FAX_DATA_BLOCK_SIZE) + except IOError: + log.error("Unable to read fax file.") + current_state = FAILURE + continue + + if data == '': + log.error("No data!") + current_state = FAILURE + break + + if self.check_for_cancel(): + current_state = FAILURE + log.error("Job is cancelled. Aborting...") + break + + total_read += FAX_DATA_BLOCK_SIZE + + try: + ret_value = self.dev.send_packet_for_message(FAX_DATA_BLOCK, 0, 0, 0, len(data)) + if ret_value: + log.debug("Sending fax-data-block request failed with %d" % ret_value) + current_state = FAILURE + else: + log.debug("Successfully sent fax-data-block request") + + self.dev.writeMarvellFax(data) + #self.dev.closeMarvellFax() + except Error: + log.error("Channel write error.") + current_state = FAILURE + break + + bytes_to_write = bytes_to_write - FAX_DATA_BLOCK_SIZE + + page.truncate(0) + page.seek(0) + + + elif fax_send_state == FAX_SEND_STATE_END_FILE_DATA: # -------------- end-of-data + log.debug("%s State: Send end-of-file-data request" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_END_JOB_REQUEST + + ret_value = self.dev.send_packet_for_message(FAX_DATA_BLOCK, 0, 0, current_state, 0) + if ret_value: + log.debug("Sending fax-data-block packet failed with %d" % ret_value) + current_state = FAILURE + else: + log.debug("Successfully sent fax-data-block request") + ret_buf = self.dev.read_response_for_message(SEND_FAX_JOB) + dev_response = self.dev.libfax_marvell.extract_response(ret_buf) + if dev_response: + log.debug("send-fax request failed with %d" % dev_response) + current_state = FAILURE + else: + log.debug("send-fax request is successful") + + if current_state: + log.debug("Exiting...") + sys.exit(1) + + + elif fax_send_state == FAX_SEND_STATE_END_JOB_REQUEST: # -------------- Wait for complete + log.debug("%s State: End the job" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_NEXT_FILE + + ret_value = self.dev.send_packet_for_message(END_FAX_JOB, 0, 0, current_state, 0) + if ret_value: + log.debug("Sending end-fax-job packet failed with %d" % ret_value) + current_state = FAILURE + else: + log.debug("Successfully sent end-fax-job request") + ret_buf = self.dev.read_response_for_message(END_FAX_JOB) + dev_response = self.dev.libfax_marvell.extract_response(ret_buf) + if dev_response: + log.debug("end-fax-job request failed with %d" % dev_response) + current_state = FAILURE + else: + log.debug("end-fax-job request is successful") + + if current_state != SUCCESS: + # There was an error during transmission... + log.error("An error occurred! setting fax_send_state to DONE") + fax_send_state = FAX_SEND_STATE_DONE + + try: + ff.close() + except NameError: + pass + + time.sleep(1) + + self.dev.close() + + + elif state == STATE_CLEANUP: # --------------------------------- Cleanup + 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 + + diff --git a/fax/pmlfax.py b/fax/pmlfax.py new file mode 100644 index 0000000..45a55f2 --- /dev/null +++ b/fax/pmlfax.py @@ -0,0 +1,1026 @@ +# -*- 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 +# + +# Std Lib +import sys +import os +import os.path +import struct +import time +import threading + +# Local +from base.g import * +from base.codes import * +from base import device, utils, pml, codes +from prnt import cups +from fax import * + + +# **************************************************************************** # + +# Page flags +PAGE_FLAG_NONE = 0x00 +PAGE_FLAG_NEW_PAGE = 0x01 +PAGE_FLAG_END_PAGE = 0x02 +PAGE_FLAG_NEW_DOC = 0x04 +PAGE_FLAG_END_DOC = 0x08 +PAGE_FLAG_END_STREAM = 0x10 + +MAJOR_VER = 2 +MINOR_VER = 0 + +MFPDTF_RASTER_BITMAP = 0 # Not used +MFPDTF_RASTER_GRAYMAP = 1 # Not used +MFPDTF_RASTER_MH = 2 # OfficeJets B&W Fax +MFPDTF_RASTER_MR = 3 # Not used +MFPDTF_RASTER_MMR = 4 # LaserJets B&W Fax +MFPDTF_RASTER_RGB = 5 # Not used +MFPDTF_RASTER_YCC411 = 6 # Not used +MFPDTF_RASTER_JPEG = 7 # Color Fax +MFPDTF_RASTER_PCL = 8 # Not used +MFPDTF_RASTER_NOT = 9 # Not used + +# Data types for FH +DT_UNKNOWN = 0 +DT_FAX_IMAGES = 1 +DT_SCANNED_IMAGES= 2 +DT_DIAL_STRINGS = 3 +DT_DEMO_PAGES = 4 +DT_SPEED_DIALS = 5 +DT_FAX_LOGS = 6 +DT_CFG_PARMS = 7 +DT_LANG_STRS = 8 +DT_JUNK_FAX_CSIDS= 9 +DT_REPORT_STRS = 10 +DT_FONTS = 11 +DT_TTI_BITMAP = 12 +DT_COUNTERS = 13 +DT_DEF_PARMS = 14 +DT_SCAN_OPTIONS = 15 +DT_FW_JOB_TABLE = 17 + +# Raster data record types +RT_START_PAGE = 0 +RT_RASTER = 1 +RT_END_PAGE = 2 + +# FH +FIXED_HEADER_SIZE = 8 + +# Variants +IMAGE_VARIANT_HEADER_SIZE = 10 +DIAL_STRINGS_VARIANT_HEADER_SIZE = 6 +FAX_IMAGE_VARIANT_HEADER_SIZE = 74 + +# Data records +SOP_RECORD_SIZE = 36 +RASTER_RECORD_SIZE = 4 +EOP_RECORD_SIZE = 12 +DIAL_STRING_RECORD_SIZE = 51 + +# Page flags +PAGE_FLAG_NEW_PAGE = 0x01 +PAGE_FLAG_END_PAGE = 0x02 +PAGE_FLAG_NEW_DOC = 0x04 +PAGE_FLAG_END_DOC = 0x08 +PAGE_FLAG_END_STREAM = 0x10 + +# Fax data variant header data source +SRC_UNKNOWN = 0 +SRC_HOST = 2 +SRC_SCANNER = 5 +SRC_HOST_THEN_SCANNER = 6 +SRC_SCANNER_THEN_HOST = 7 + +# Fax data variant header TTI header control +TTI_NONE = 0 +TTI_PREPENDED_TO_IMAGE = 1 +TTI_OVERLAYED_ON_IMAGE = 2 + +RASTER_DATA_SIZE = 504 + + + +# **************************************************************************** # +class PMLFaxDevice(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 + + + def setPhoneNum(self, num): + return self.setPML(pml.OID_FAX_LOCAL_PHONE_NUM, str(num)) + + def getPhoneNum(self): + return utils.printable(str(self.getPML(pml.OID_FAX_LOCAL_PHONE_NUM)[1])) + + phone_num = property(getPhoneNum, setPhoneNum, doc="OID_FAX_LOCAL_PHONE_NUM") + + + def setStationName(self, name): + return self.setPML(pml.OID_FAX_STATION_NAME, str(name)) + + def getStationName(self): + return utils.printable(str(self.getPML(pml.OID_FAX_STATION_NAME)[1])) + + station_name = property(getStationName, setStationName, doc="OID_FAX_STATION_NAME") + + def setDateAndTime(self): + t = time.localtime() + p = struct.pack("BBBBBBB", t[0]-2000, t[1], t[2], t[6]+1, t[3], t[4], t[5]) + log.debug(repr(p)) + return self.setPML(pml.OID_DATE_AND_TIME, p) + + def uploadLog(self): + if not self.isUloadLogActive(): + self.upload_log_thread = UploadLogThread(self) + self.upload_log_thread.start() + return True + else: + return False + + def isUploadLogActive(self): + if self.upload_log_thread is not None: + return self.upload_log_thread.isAlive() + else: + return False + + def waitForUploadLogThread(self): + if self.upload_log_thread is not None and \ + self.upload_log_thread.isAlive(): + + self.upload_log_thread.join() + + 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 = PMLFaxSendThread(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 PMLUploadLogThread(threading.Thread): + def __init__(self, dev): + threading.Thread.__init__(self) + self.dev = dev + + + def run(self): + STATE_DONE = 0 + STATE_ABORT = 10 + STATE_SUCCESS = 20 + STATE_BUSY = 25 + STATE_DEVICE_OPEN = 28 + STATE_CHECK_IDLE = 30 + STATE_REQUEST_START = 40 + STATE_WAIT_FOR_ACTIVE = 50 + STATE_UPLOAD_DATA = 60 + STATE_DEVICE_CLOSE = 70 + + state = STATE_CHECK_IDLE + + while state != STATE_DONE: # --------------------------------- Log upload state machine + if state == STATE_ABORT: + pass + elif state == STATE_SUCCESS: + pass + elif state == STATE_BUSY: + pass + + elif state == STATE_DEVICE_OPEN: # --------------------------------- Open device (28) + state = STATE_REQUEST_START + try: + self.dev.open() + except Error, e: + log.error("Unable to open device (%s)." % e.msg) + state = STATE_ERROR + else: + try: + dev.setPML(pml.OID_UPLOAD_TIMEOUT, pml.DEFAULT_UPLOAD_TIMEOUT) + except Error: + state = STATE_ERROR + + elif state == STATE_CHECK_IDLE: # --------------------------------- Check idle (30) + state = STATE_REQUEST_START + ul_state = self.getCfgUploadState() + + if ul_state != pml.UPDN_STATE_IDLE: + state = STATE_BUSY + + + elif state == STATE_REQUEST_START: # --------------------------------- Request start (40) + state = STATE_WAIT_FOR_ACTIVE + self.dev.setPML(pml.OID_FAX_CFG_UPLOAD_DATA_TYPE, pml.FAX_CFG_UPLOAD_DATA_TYPE_FAXLOGS) + self.dev.setPML(pml.OID_DEVICE_CFG_UPLOAD, pml.UPDN_STATE_REQSTART) + + elif state == STATE_WAIT_FOR_ACTIVE: # --------------------------------- Wait for active state (50) + state = STATE_UPLOAD_DATA + + tries = 0 + while True: + tries += 1 + ul_state = self.getCfgUploadState() + + if ul_state == pml.UPDN_STATE_XFERACTIVE: + break + + if ul_state in (pml.UPDN_STATE_ERRORABORT, pml.UPDN_STATE_XFERDONE): + log.error("Cfg upload aborted!") + state = STATE_ERROR + break + + if tries > 10: + state = STATE_ERROR + log.error("Unable to get into active state!") + break + + time.sleep(0.5) + + elif state == STATE_UPLOAD_DATA: # --------------------------------- Upload log data (60) + pass + + elif state == STATE_DEVICE_CLOSE: # --------------------------------- Close device (70) + self.dev.close() + + + +# **************************************************************************** # +class PMLFaxSendThread(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) + + + 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("PML get failed!") + state = STATE_ERROR + + finally: + self.dev.close() + + + elif state == STATE_PRERENDER: # --------------------------------- Pre-render non-G3 files (40, 0, 0) + log.debug("%s State: Pre-render non-G3 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() + #print recipient + 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 + + self.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 G3 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_SET_TOKEN = 50 + FAX_SEND_STATE_EARLY_OPEN = 60 + FAX_SEND_STATE_SET_PARAMS = 70 + FAX_SEND_STATE_CHECK_IDLE = 80 + FAX_SEND_STATE_START_REQUEST = 90 + FAX_SEND_STATE_LATE_OPEN = 100 + FAX_SEND_STATE_SEND_DIAL_STRINGS = 110 + FAX_SEND_STATE_SEND_FAX_HEADER = 120 + FAX_SEND_STATE_SEND_PAGES = 130 + FAX_SEND_STATE_SEND_END_OF_STREAM = 140 + FAX_SEND_STATE_WAIT_FOR_COMPLETE = 150 + FAX_SEND_STATE_RESET_TOKEN = 160 + FAX_SEND_STATE_CLOSE_SESSION = 170 + + monitor_state = False + error_state = pml.DN_ERROR_NONE + 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) + # TODO: Set D/L state to ??? + monitor_state = False + fax_send_state = FAX_SEND_STATE_RESET_TOKEN + state = STATE_ABORTED + + elif fax_send_state == FAX_SEND_STATE_ERROR: # -------------- Error (110, 20, 0) + log.error("Fax send error.") + error_state = self.getFaxDownloadError() + log.debug("Error State=%d (%s)" % (error_state, pml.DN_ERROR_STR.get(error_state, "Unknown"))) + monitor_state = False + + fax_send_state = FAX_SEND_STATE_RESET_TOKEN + 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_RESET_TOKEN + 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_RESET_TOKEN + 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_SET_TOKEN + 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_SET_TOKEN: # -------------- Acquire fax token (110, 50, 0) + log.debug("%s State: Acquire fax token" % ("*"*20)) + try: + result_code, token = self.dev.getPML(pml.OID_FAX_TOKEN) + except Error: + log.debug("Unable to acquire fax token (1).") + fax_send_state = FAX_SEND_STATE_EARLY_OPEN + else: + if result_code > pml.ERROR_MAX_OK: + fax_send_state = FAX_SEND_STATE_EARLY_OPEN + log.debug("Skipping token acquisition.") + else: + token = time.strftime("%d%m%Y%H:%M:%S", time.gmtime()) + log.debug("Setting token: %s" % token) + try: + self.dev.setPML(pml.OID_FAX_TOKEN, token) + except Error: + log.error("Unable to acquire fax token (2).") + fax_send_state = FAX_SEND_STATE_ERROR + else: + result_code, check_token = self.dev.getPML(pml.OID_FAX_TOKEN) + + if check_token == token: + fax_send_state = FAX_SEND_STATE_EARLY_OPEN + else: + log.error("Unable to acquire fax token (3).") + fax_send_state = FAX_SEND_STATE_ERROR + + + elif fax_send_state == FAX_SEND_STATE_EARLY_OPEN: # -------------- Early open (newer models) (110, 60, 0) + log.debug("%s State: Early open" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_CHECK_IDLE + + if self.dev.fax_type == FAX_TYPE_BLACK_SEND_EARLY_OPEN: # newer + log.debug("Opening fax channel.") + try: + self.dev.openFax() + except Error, e: + log.error("Unable to open channel (%s)." % e.msg) + fax_send_state = FAX_SEND_STATE_ERROR + else: + log.debug("Skipped.") + + + elif fax_send_state == FAX_SEND_STATE_CHECK_IDLE: # -------------- Check for initial idle (110, 80, 0) + log.debug("%s State: Check idle" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_START_REQUEST + + dl_state = self.getFaxDownloadState() + tx_status = self.getFaxJobTxStatus() + rx_status = self.getFaxJobRxStatus() + + if ((dl_state == pml.UPDN_STATE_IDLE or \ + dl_state == pml.UPDN_STATE_ERRORABORT or \ + dl_state == pml.UPDN_STATE_XFERDONE) and \ + (tx_status == pml.FAXJOB_TX_STATUS_IDLE or tx_status == pml.FAXJOB_TX_STATUS_DONE) and \ + (rx_status == pml.FAXJOB_RX_STATUS_IDLE or rx_status == pml.FAXJOB_RX_STATUS_DONE)): + + # xwas if state == pml.UPDN_STATE_IDLE: + if dl_state == pml.UPDN_STATE_IDLE: + log.debug("Starting in idle state") + else: + log.debug("Resetting to idle...") + self.dev.setPML(pml.OID_FAX_DOWNLOAD, pml.UPDN_STATE_IDLE) + time.sleep(0.5) + else: + fax_send_state = FAX_SEND_STATE_BUSY + + elif fax_send_state == FAX_SEND_STATE_START_REQUEST: # -------------- Request fax start (110, 90, 0) + log.debug("%s State: Request start" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SET_PARAMS + + dl_state = self.getFaxDownloadState() + + if dl_state == pml.UPDN_STATE_IDLE: + log.debug("Try: 0") + log.debug("Setting to up/down state request start...") + self.dev.setPML(pml.OID_FAX_DOWNLOAD, pml.UPDN_STATE_REQSTART) + time.sleep(1) + + log.debug("Waiting for active state...") + i = 1 + + while i < 10: + log.debug("Try: %d" % i) + try: + dl_state = self.getFaxDownloadState() + except Error: + log.error("PML/SNMP error") + fax_send_state = FAX_SEND_STATE_ERROR + break + + if dl_state == pml.UPDN_STATE_XFERACTIVE: + break + + time.sleep(1) + log.debug("Setting to up/down state request start...") + self.dev.setPML(pml.OID_FAX_DOWNLOAD, pml.UPDN_STATE_REQSTART) + + i += 1 + + else: + log.error("Could not get into active state!") + fax_send_state = FAX_SEND_STATE_BUSY + + monitor_state = True + + else: + log.error("Could not get into idle state!") + fax_send_state = FAX_SEND_STATE_BUSY + + + elif fax_send_state == FAX_SEND_STATE_SET_PARAMS: # -------------- Set fax send params (110, 70, 0) + log.debug("%s State: Set params" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_LATE_OPEN + + try: + self.dev.setPML(pml.OID_DEV_DOWNLOAD_TIMEOUT, pml.DEFAULT_DOWNLOAD_TIMEOUT) + self.dev.setPML(pml.OID_FAXJOB_TX_TYPE, pml.FAXJOB_TX_TYPE_HOST_ONLY) + log.debug("Setting date and time on device.") + self.dev.setDateAndTime() + except Error, e: + log.error("PML/SNMP error (%s)" % e.msg) + fax_send_state = FAX_SEND_STATE_ERROR + + + elif fax_send_state == FAX_SEND_STATE_LATE_OPEN: # -------------- Late open (older models) (110, 100, 0) + log.debug("%s State: Late open" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SEND_DIAL_STRINGS + + if self.dev.fax_type == FAX_TYPE_BLACK_SEND_LATE_OPEN: # older + log.debug("Opening fax channel.") + try: + self.dev.openFax() + except Error: + log.error("Unable to open channel.") + fax_send_state = FAX_SEND_STATE_ERROR + else: + log.debug("Skipped.") + + + elif fax_send_state == FAX_SEND_STATE_SEND_DIAL_STRINGS: # -------------- Dial strings (110, 110, 0) + log.debug("%s State: Send dial strings" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SEND_FAX_HEADER + + log.debug("Dialing: %s" % recipient['fax']) + + log.debug("Sending dial strings...") + self.create_mfpdtf_fixed_header(DT_DIAL_STRINGS, True, + PAGE_FLAG_NEW_DOC | PAGE_FLAG_END_DOC | PAGE_FLAG_END_STREAM) # 0x1c on Windows, we were sending 0x0c + #print recipient + dial_strings = recipient['fax'].encode('ascii') + log.debug(repr(dial_strings)) + self.create_mfpdtf_dial_strings(dial_strings) + + try: + self.write_stream() + except Error: + log.error("Channel write error.") + fax_send_state = FAX_SEND_STATE_ERROR + + + elif fax_send_state == FAX_SEND_STATE_SEND_FAX_HEADER: # -------------- Fax header (110, 120, 0) + log.debug("%s State: Send fax header" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SEND_PAGES + + 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)) + + log.debug("Sending fax header...") + self.create_mfpdtf_fixed_header(DT_FAX_IMAGES, True, PAGE_FLAG_NEW_DOC) + self.create_mfpdtf_fax_header(total_pages) + + try: + self.write_stream() + except Error: + log.error("Unable to write to channel.") + fax_send_state = FAX_SEND_STATE_ERROR + + + elif fax_send_state == FAX_SEND_STATE_SEND_PAGES: # --------------------------------- Send fax pages state machine (110, 130, 0) + log.debug("%s State: Send pages" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_SEND_END_OF_STREAM + 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)) + + page.write(ff.read(bytes_to_read)) + thumbnail = ff.read(thumbnail_bytes) # thrown away for now (should be 0 read) + page.seek(0) + + self.create_mfpdtf_fixed_header(DT_FAX_IMAGES, page_flags=PAGE_FLAG_NEW_PAGE) + self.create_sop_record(page_num, hort_dpi, vert_dpi, ppr, rpp, encoding) + + try: + data = page.read(RASTER_DATA_SIZE) + except IOError: + log.error("Unable to read fax file.") + fax_send_state = FAX_SEND_STATE_ERROR + continue + + if data == '': + log.error("No data!") + fax_send_state = FAX_SEND_STATE_ERROR + continue + + self.create_raster_data_record(data) + total_read = RASTER_DATA_SIZE + + while True: + data = page.read(RASTER_DATA_SIZE) + total_read += RASTER_DATA_SIZE + + dl_state = self.getFaxDownloadState() + if dl_state == pml.UPDN_STATE_ERRORABORT: + fax_send_state = FAX_SEND_STATE_ERROR + break + + if self.check_for_cancel(): + fax_send_state = FAX_SEND_STATE_ABORT + break + + if data == '': + self.create_eop_record(rpp) + + try: + self.write_stream() + except Error: + log.error("Channel write error.") + fax_send_state = FAX_SEND_STATE_ERROR + break + + else: + try: + self.write_stream() + except Error: + log.error("Channel write error.") + fax_send_state = FAX_SEND_STATE_ERROR + break + + status = self.getFaxJobTxStatus() + while status == pml.FAXJOB_TX_STATUS_DIALING: + self.write_queue((STATUS_DIALING, 0, recipient['fax'])) + time.sleep(1.0) + + if self.check_for_cancel(): + fax_send_state = FAX_SEND_STATE_ABORT + break + + dl_state = self.getFaxDownloadState() + if dl_state == pml.UPDN_STATE_ERRORABORT: + fax_send_state = FAX_SEND_STATE_ERROR + break + + status = self.getFaxJobTxStatus() + + if fax_send_state not in (FAX_SEND_STATE_ABORT, FAX_SEND_STATE_ERROR): + + while status == pml.FAXJOB_TX_STATUS_CONNECTING: + self.write_queue((STATUS_CONNECTING, 0, recipient['fax'])) + time.sleep(1.0) + + if self.check_for_cancel(): + fax_send_state = FAX_SEND_STATE_ABORT + break + + dl_state = self.getFaxDownloadState() + if dl_state == pml.UPDN_STATE_ERRORABORT: + fax_send_state = FAX_SEND_STATE_ERROR + break + + status = self.getFaxJobTxStatus() + + if status == pml.FAXJOB_TX_STATUS_TRANSMITTING: + self.write_queue((STATUS_SENDING, page_num, recipient['fax'])) + + self.create_mfpdtf_fixed_header(DT_FAX_IMAGES, page_flags=0) + self.create_raster_data_record(data) + + if fax_send_state in (FAX_SEND_STATE_ABORT, FAX_SEND_STATE_ERROR): + break + + page.truncate(0) + page.seek(0) + + + elif fax_send_state == FAX_SEND_STATE_SEND_END_OF_STREAM: # -------------- EOS (110, 140, 0) + log.debug("%s State: Send EOS" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_WAIT_FOR_COMPLETE + log.debug("End of stream...") + self.create_mfpdtf_fixed_header(DT_FAX_IMAGES, False, PAGE_FLAG_END_STREAM) + + try: + self.write_stream() + except Error: + log.error("Channel write error.") + fax_send_state = FAX_SEND_STATE_ERROR + + monitor_state = False + + + elif fax_send_state == FAX_SEND_STATE_WAIT_FOR_COMPLETE: # -------------- Wait for complete (110, 150, 0) + log.debug("%s State: Wait for completion" % ("*"*20)) + + fax_send_state = FAX_SEND_STATE_WAIT_FOR_COMPLETE + + time.sleep(1.0) + status = self.getFaxJobTxStatus() + + if status == pml.FAXJOB_TX_STATUS_DIALING: + self.write_queue((STATUS_DIALING, 0, recipient['fax'])) + log.debug("Dialing ...") + + elif status == pml.FAXJOB_TX_STATUS_TRANSMITTING: + self.write_queue((STATUS_SENDING, page_num, recipient['fax'])) + log.debug("Transmitting ...") + + elif status in (pml.FAXJOB_TX_STATUS_DONE, pml.FAXJOB_RX_STATUS_IDLE): + fax_send_state = FAX_SEND_STATE_RESET_TOKEN + state = STATE_NEXT_RECIPIENT + log.debug("Transmitting done or idle ...") + + else: + self.write_queue((STATUS_SENDING, page_num, recipient['fax'])) + log.debug("Pending ...") + + + elif fax_send_state == FAX_SEND_STATE_RESET_TOKEN: # -------------- Release fax token (110, 160, 0) + log.debug("%s State: Release fax token" % ("*"*20)) + self.write_queue((STATUS_CLEANUP, 0, '')) + + try: + self.dev.setPML(pml.OID_FAX_TOKEN, '\x00'*16) + except Error: + log.error("Unable to release fax token.") + + fax_send_state = FAX_SEND_STATE_CLOSE_SESSION + + + elif fax_send_state == FAX_SEND_STATE_CLOSE_SESSION: # -------------- Close session (110, 170, 0) + log.debug("%s State: Close session" % ("*"*20)) + fax_send_state = FAX_SEND_STATE_DONE + log.debug("Closing session...") + + try: + mm.close() + except NameError: + pass + + try: + ff.close() + except NameError: + pass + + if self.dev.fax_type == FAX_TYPE_BLACK_SEND_LATE_OPEN: + log.debug("Closing fax channel.") + self.dev.closeFax() + + self.dev.setPML(pml.OID_FAX_DOWNLOAD, pml.UPDN_STATE_IDLE) + + time.sleep(1) + + if self.dev.fax_type == FAX_TYPE_BLACK_SEND_EARLY_OPEN: + log.debug("Closing fax channel.") + self.dev.closeFax() + + self.dev.close() + + + 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 + + + +# --------------------------------- Support functions + + + def getFaxDownloadState(self): + result_code, state = self.dev.getPML(pml.OID_FAX_DOWNLOAD) + if state: + log.debug("D/L State=%d (%s)" % (state, pml.UPDN_STATE_STR.get(state, 'Unknown'))) + return state + else: + return pml.UPDN_STATE_ERRORABORT + + def getFaxDownloadError(self): + result_code, state = self.dev.getPML(pml.OID_FAX_DOWNLOAD_ERROR) + if state: + return state + else: + return pml.DN_ERROR_UNKNOWN + + def getFaxJobTxStatus(self): + result_code, status = self.dev.getPML(pml.OID_FAXJOB_TX_STATUS) + if status: + log.debug("Tx Status=%d (%s)" % (status, pml.FAXJOB_TX_STATUS_STR.get(status, 'Unknown'))) + return status + else: + return pml.FAXJOB_TX_STATUS_IDLE + + def getFaxJobRxStatus(self): + result_code, status = self.dev.getPML(pml.OID_FAXJOB_RX_STATUS) + if status: + log.debug("Rx Status=%d (%s)" % (status, pml.FAXJOB_RX_STATUS_STR.get(status, 'Unknown'))) + return status + else: + return pml.FAXJOB_RX_STATUS_IDLE + + def getCfgUploadState(self): + result_code, state = self.dev.getPML(pml.OID_DEVICE_CFG_UPLOAD) + if state: + log.debug("Cfg Upload State = %d (%s)" % (state, pml.UPDN_STATE_STR.get(state, 'Unknown'))) + return state + else: + return pml.UPDN_STATE_ERRORABORT + + def create_mfpdtf_fixed_header(self, data_type, send_variant=False, page_flags=0): + header_len = FIXED_HEADER_SIZE + + if send_variant: + if data_type == DT_DIAL_STRINGS: + header_len += DIAL_STRINGS_VARIANT_HEADER_SIZE + + elif data_type == DT_FAX_IMAGES: + header_len += FAX_IMAGE_VARIANT_HEADER_SIZE + + self.stream.write(struct.pack("<IHBB", + 0, header_len, data_type, page_flags)) + + + def create_mfpdtf_dial_strings(self, number): + p = struct.pack("<BBHH51s", + MAJOR_VER, MINOR_VER, + 1, 51, number[:51]) + log.debug(repr(p)) + self.stream.write(p) + + + def adjust_fixed_header_block_size(self): + size = self.stream.tell() + self.stream.seek(0) + self.stream.write(struct.pack("<I", size)) + + + def create_sop_record(self, page_num, hort_dpi, vert_dpi, ppr, rpp, encoding, bpp=1): + self.stream.write(struct.pack("<BBHHHIHHHHHHIHHHH", + RT_START_PAGE, encoding, page_num, + ppr, bpp, + rpp, 0x00, hort_dpi, 0x00, vert_dpi, + ppr, bpp, + rpp, 0x00, hort_dpi, 0x00, vert_dpi)) + + + def create_eop_record(self, rpp): + self.stream.write(struct.pack("<BBBBII", + RT_END_PAGE, 0, 0, 0, + rpp, 0,)) + + + def create_raster_data_record(self, data): + assert len(data) <= RASTER_DATA_SIZE + self.stream.write(struct.pack("<BBH", + RT_RASTER, 0, len(data),)) + self.stream.write(data) + + + def create_mfpdtf_fax_header(self, total_pages): + self.stream.write(struct.pack("<BBBHBI20s20s20sI", + MAJOR_VER, MINOR_VER, SRC_HOST, total_pages, + TTI_PREPENDED_TO_IMAGE, 0, '', '', '', 0)) + + + def write_stream(self): + self.adjust_fixed_header_block_size() + self.dev.writeFax(self.stream.getvalue()) + self.stream.truncate(0) + self.stream.seek(0) diff --git a/fax/ppd/HP-Fax-hpcups.ppd.gz b/fax/ppd/HP-Fax-hpcups.ppd.gz Binary files differnew file mode 100644 index 0000000..bafd439 --- /dev/null +++ b/fax/ppd/HP-Fax-hpcups.ppd.gz diff --git a/fax/ppd/HP-Fax-hpijs.ppd.gz b/fax/ppd/HP-Fax-hpijs.ppd.gz Binary files differnew file mode 100644 index 0000000..612bcaf --- /dev/null +++ b/fax/ppd/HP-Fax-hpijs.ppd.gz diff --git a/fax/ppd/HP-Fax2-hpcups.ppd.gz b/fax/ppd/HP-Fax2-hpcups.ppd.gz Binary files differnew file mode 100644 index 0000000..ffc35f3 --- /dev/null +++ b/fax/ppd/HP-Fax2-hpcups.ppd.gz diff --git a/fax/ppd/HP-Fax2-hpijs.ppd.gz b/fax/ppd/HP-Fax2-hpijs.ppd.gz Binary files differnew file mode 100644 index 0000000..b228073 --- /dev/null +++ b/fax/ppd/HP-Fax2-hpijs.ppd.gz diff --git a/fax/ppd/HP-Fax3-hpcups.ppd.gz b/fax/ppd/HP-Fax3-hpcups.ppd.gz Binary files differnew file mode 100644 index 0000000..4e289b3 --- /dev/null +++ b/fax/ppd/HP-Fax3-hpcups.ppd.gz diff --git a/fax/ppd/HP-Fax3-hpijs.ppd.gz b/fax/ppd/HP-Fax3-hpijs.ppd.gz Binary files differnew file mode 100644 index 0000000..cb6116c --- /dev/null +++ b/fax/ppd/HP-Fax3-hpijs.ppd.gz diff --git a/fax/ppd/HP-Fax4-hpcups.ppd.gz b/fax/ppd/HP-Fax4-hpcups.ppd.gz Binary files differnew file mode 100644 index 0000000..40a6f5b --- /dev/null +++ b/fax/ppd/HP-Fax4-hpcups.ppd.gz diff --git a/fax/ppd/HP-Fax4-hpijs.ppd.gz b/fax/ppd/HP-Fax4-hpijs.ppd.gz Binary files differnew file mode 100644 index 0000000..2fee3b0 --- /dev/null +++ b/fax/ppd/HP-Fax4-hpijs.ppd.gz diff --git a/fax/soapfax.py b/fax/soapfax.py new file mode 100644 index 0000000..01af1cc --- /dev/null +++ b/fax/soapfax.py @@ -0,0 +1,719 @@ +# -*- 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( +"""<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>""") + + 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( +"""<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>""") + + 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( +"""<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>""") + + 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( +"""<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>""") + + 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""") + + + + |