Replace 'tap' to 'spaces' to make gbs build succeed
[platform/upstream/hplip.git] / sendfax.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 #
20 # Author: Don Welch
21 #
22 # Thanks to Henrique M. Holschuh <hmh@debian.org> for various security patches
23 #
24
25 __version__ = '9.0'
26 __title__ = 'PC Sendfax Utility'
27 __mod__ = 'hp-sendfax'
28 __doc__ = "PC send fax for HPLIP supported multifunction printers."
29
30 # Std Lib
31 import sys
32 import os
33 import os.path
34 import getopt
35 import signal
36 import time
37 import operator
38
39 # Local
40 from base.g import *
41 import base.utils as utils
42 from base import device, tui, module
43
44 username = prop.username
45 faxnum_list = []
46 recipient_list = []
47 group_list = []
48 prettyprint = False
49
50 mod = module.Module(__mod__, __title__, __version__, __doc__, None,
51                     (GUI_MODE, NON_INTERACTIVE_MODE),
52                     (UI_TOOLKIT_QT3, UI_TOOLKIT_QT4))
53
54 mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS | module.USAGE_FLAG_SUPRESS_G_DEBUG_FLAG,
55     extra_options=[
56     ("Specify the fax number(s):", "-f<number(s)> or --faxnum=<number(s)> or --fax-num=<number(s)>  or --num=<number(s)>(-n only)", "option", False),
57     ("Specify the recipient(s):", "-r<recipient(s)> or --recipient=<recipient(s)> (-n only)", "option", False),
58     ("Specify the groups(s):", "--group=<group(s)> or --groups=<group(s)> (-n only)", "option", False) ],
59     see_also_list=['hp-faxsetup', 'hp-fab'])
60
61 opts, device_uri, printer_name, mode, ui_toolkit, loc = \
62     mod.parseStdOpts('f:r:g:',
63                      ['faxnum=', 'fax-num=', 'recipient=', 'group=',
64                       'groups=', 'gg'],
65                       supress_g_debug_flag=True)
66
67 for o, a in opts:
68     if o == '--gg':
69         log.set_level('debug')
70
71     elif o in ('-z', '--logfile'):
72         log.set_logfile(a)
73         log.set_where(log.LOG_TO_CONSOLE_AND_FILE)
74
75     elif o == '--fax':
76         printer_name = a
77
78     elif o in ('-f', '--faxnum', '--fax-num', '--num'):
79         faxnum_list.extend(a.split(','))
80
81     elif o in ('-r', '--recipient'):
82         recipient_list.extend(a.split(','))
83
84     elif o in ('-g', '--group'):
85         group_list.extend(a.split(','))
86
87
88 if not prop.fax_build:
89     log.error("Fax is disabled (turned off during build). Exiting")
90     sys.exit(1)
91
92 printer_name, device_uri = mod.getPrinterName(printer_name, device_uri,
93     filter={'fax-type': (operator.gt, 0)}, back_end_filter=['hpfax'])
94
95 if mode == GUI_MODE:
96     if ui_toolkit == 'qt3':
97         if not utils.canEnterGUIMode():
98             log.error("%s requires GUI support (try running with --qt4). Also, try using non-interactive (-n) mode." % __mod__)
99             sys.exit(1)
100     else:
101         if not utils.canEnterGUIMode4():
102             log.error("%s requires GUI support (try running with --qt3). Also, try using non-interactive (-n) mode." % __mod__)
103             sys.exit(1)
104
105 if mode == GUI_MODE:
106     if ui_toolkit == 'qt3':
107         app = None
108         sendfax = None
109
110         try:
111             from qt import *
112             from ui.faxsendjobform import FaxSendJobForm
113         except ImportError:
114             log.error("Unable to load Qt3 support. Is it installed?")
115             sys.exit(1)
116
117         # create the main application object
118         app = QApplication(sys.argv)
119
120         if loc is None:
121             loc = user_conf.get('ui', 'loc', 'system')
122             if loc.lower() == 'system':
123                 loc = str(QTextCodec.locale())
124                 log.debug("Using system locale: %s" % loc)
125
126         if loc.lower() != 'c':
127             e = 'utf8'
128             try:
129                 l, x = loc.split('.')
130                 loc = '.'.join([l, e])
131             except ValueError:
132                 l = loc
133                 loc = '.'.join([loc, e])
134
135             log.debug("Trying to load .qm file for %s locale." % loc)
136             trans = QTranslator(None)
137
138             qm_file = 'hplip_%s.qm' % l
139             log.debug("Name of .qm file: %s" % qm_file)
140             loaded = trans.load(qm_file, prop.localization_dir)
141
142             if loaded:
143                 app.installTranslator(trans)
144             else:
145                 loc = 'c'
146
147         if loc == 'c':
148             log.debug("Using default 'C' locale")
149         else:
150             log.debug("Using locale: %s" % loc)
151
152             QLocale.setDefault(QLocale(loc))
153             prop.locale = loc
154             try:
155                 locale.setlocale(locale.LC_ALL, locale.normalize(loc))
156             except locale.Error:
157                 pass
158
159
160         if os.geteuid() == 0:
161             log.error("You must not be root to run this utility.")
162
163             QMessageBox.critical(None,
164                                  "HP Device Manager - Send Fax",
165                                  "You must not be root to run hp-sendfax.",
166                                   QMessageBox.Ok,
167                                   QMessageBox.NoButton,
168                                   QMessageBox.NoButton)
169
170             sys.exit(1)
171
172         # TODO: Fix instance lock
173         sendfax = FaxSendJobForm(device_uri,
174                                  printer_name,
175                                  mod.args)
176
177         app.setMainWidget(sendfax)
178
179         pid = os.getpid()
180         log.debug('pid=%d' % pid)
181
182         sendfax.show()
183
184         try:
185             log.debug("Starting GUI loop...")
186             app.exec_loop()
187         except KeyboardInterrupt:
188             pass
189
190     else: # qt4
191         #try:
192         if 1:
193             from PyQt4.QtGui import QApplication
194             from ui4.sendfaxdialog import SendFaxDialog
195         #except ImportError:
196         if 0:
197             log.error("Unable to load Qt4 support. Is it installed?")
198             sys.exit(1)
199
200         app = QApplication(sys.argv)
201
202         dlg = SendFaxDialog(None, printer_name, device_uri, mod.args)
203         dlg.show()
204
205         try:
206             log.debug("Starting GUI loop...")
207             app.exec_()
208         except KeyboardInterrupt:
209             sys.exit(0)
210
211
212
213
214 else: # NON_INTERACTIVE_MODE
215     if os.getuid() == 0:
216         log.error("%s cannot be run as root." % __mod__)
217         sys.exit(1)
218
219     try:
220         import struct, Queue
221         from prnt import cups
222         from base import magic
223
224         try:
225             from fax import fax
226         except ImportError:
227             # This can fail on Python < 2.3 due to the datetime module
228             log.error("Fax address book disabled - Python 2.3+ required.")
229             sys.exit(1)
230
231         db =  fax.FaxAddressBook() # FAB instance
232
233         try:
234             import dbus
235         except ImportError:
236             log.error("PC send fax requires dBus and python-dbus")
237             sys.exit(1)
238
239         import warnings
240         # Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters
241         # (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04)
242         warnings.simplefilter("ignore", DeprecationWarning)
243
244         dbus_avail, service, session_bus = device.init_dbus()
245
246         if not dbus_avail or service is None:
247             log.error("Unable to initialize dBus. PC send fax requires dBus and hp-systray support. Exiting.")
248             sys.exit(1)
249
250         phone_num_list = []
251
252         log.debug("Faxnum list = %s" % faxnum_list)
253         faxnum_list = utils.uniqueList(faxnum_list)
254         log.debug("Unique list=%s" % faxnum_list)
255
256         for f in faxnum_list:
257             for c in f:
258                 if c not in '0123456789-(+) *#':
259                     log.error("Invalid character in fax number '%s'. Only the characters '0123456789-(+) *#' are valid." % f)
260                     sys.exit(1)
261
262         log.debug("Group list = %s" % group_list)
263         group_list = utils.uniqueList(group_list)
264         log.debug("Unique list=%s" % group_list)
265
266         for g in group_list:
267             entries = db.group_members(g)
268             if not entries:
269                 log.warn("Unknown group name: %s" % g)
270             else:
271                 for e in entries:
272                     recipient_list.append(e)
273
274         log.debug("Recipient list = %s" % recipient_list)
275         recipient_list = utils.uniqueList(recipient_list)
276         log.debug("Unique list=%s" % recipient_list)
277
278         for r in recipient_list:
279             if db.get(r) is None:
280                 log.error("Unknown fax recipient '%s' in the recipient list." % r)
281                 all_entries = db.get_all_records()
282                 log.info(log.bold("\nKnown recipients (entries):"))
283
284                 for a in all_entries:
285                     aa = db.get(a)
286                     log.info("%s (fax number: %s)" % (a, aa['fax']))
287
288                 print
289                 sys.exit(1)
290
291         for p in recipient_list:
292             a = db.get(p)
293             if a['fax']:
294                 phone_num_list.append(a)
295                 log.debug("Name=%s Number=%s" % (a['name'], a['fax']))
296
297         for p in faxnum_list:
298             phone_num_list.append({'fax': p, 'name': u'Unknown'})
299             log.debug("Number=%s" % p)
300
301         log.debug("Phone num list = %s" % phone_num_list)
302
303         if not phone_num_list:
304             mod.usage(error_msg=["No recipients specified. Please use -f, -r, and/or -g to specify recipients."])
305
306         allowable_mime_types = cups.getAllowableMIMETypes()
307
308         for f in mod.args:
309             path = os.path.realpath(f)
310             log.debug(path)
311
312             if os.path.exists(path):
313                 mime_type = magic.mime_type(path)
314                 log.debug(mime_type)
315             else:
316                 log.error("File '%s' does not exist." % path)
317                 sys.exit(1)
318
319             if mime_type not in allowable_mime_types:
320                 log.error("File '%s' has a non-allowed mime-type of '%s'" % (path, mime_type))
321                 sys.exit(1)
322
323         log.info(log.bold("Using fax %s (%s)" % (printer_name, device_uri)))
324
325         #ok, lock_file = utils.lock_app('%s-%s' % (__mod__, printer_name), True)
326         mod.lockInstance(printer_name)
327
328         try:
329             ppd_file = cups.getPPD(printer_name)
330
331             if ppd_file is not None and os.path.exists(ppd_file):
332                 if file(ppd_file, 'r').read(8192).find('HP Fax') == -1:
333                     log.error("Fax configuration error. The CUPS fax queue for '%s' is incorrectly configured. Please make sure that the CUPS fax queue is configured with the 'HP Fax' Model/Driver." % printer_name)
334                     sys.exit(1)
335
336             if not mod.args:
337                 mod.usage(error_msg=["No files specfied to send. Please specify the file(s) to send on the command line."])
338
339             file_list = []
340
341             for f in mod.args:
342
343                 #
344                 # Submit each file to CUPS for rendering by hpijsfax
345                 #
346                 path = os.path.realpath(f)
347                 log.debug(path)
348                 mime_type = magic.mime_type(path)
349
350                 if mime_type == 'application/hplip-fax': # .g3
351                     log.info("\nPreparing fax file %s..." % f)
352                     fax_file_fd = file(f, 'r')
353                     header = fax_file_fd.read(fax.FILE_HEADER_SIZE)
354                     fax_file_fd.close()
355
356                     mg, version, pages, hort_dpi, vert_dpi, page_size, \
357                         resolution, encoding, reserved1, reserved2 = struct.unpack(">8sBIHHBBBII", header)
358
359                     if mg != 'hplip_g3':
360                         log.error("%s: Invalid file header. Bad magic." % f)
361                         sys.exit(1)
362
363                     file_list.append((f, mime_type, "", "", pages))
364
365                 else:
366                     all_pages = True
367                     page_range = ''
368                     page_set = 0
369                     nup = 1
370
371                     cups.resetOptions()
372
373                     if mime_type in ["application/x-cshell",
374                                      "application/x-perl",
375                                      "application/x-python",
376                                      "application/x-shell",
377                                      "text/plain",] and prettyprint:
378
379                         cups.addOption('prettyprint')
380
381                     if nup > 1:
382                         cups.addOption('number-up=%d' % nup)
383
384                     while True:
385
386                         cups_printers = cups.getPrinters()
387                         printer_state = cups.IPP_PRINTER_STATE_STOPPED
388                         for p in cups_printers:
389                             if p.name == printer_name:
390                                 printer_state = p.state
391
392                         log.debug("Printer state = %d" % printer_state)
393
394                         if printer_state == cups.IPP_PRINTER_STATE_IDLE:
395                             log.debug("Printer name = %s file = %s" % (printer_name, path))
396                             sent_job_id = cups.printFile(printer_name, path, os.path.basename(path))
397                             log.info("\nRendering file '%s' (job %d)..." % (path, sent_job_id))
398                             log.debug("Job ID=%d" % sent_job_id)
399                             break
400                         elif printer_state == cups.IPP_PRINTER_STATE_PROCESSING:
401                             log.debug("Waiting for CUPS queue '%s' to become idle." % printer_name)
402                         else:
403                             log.error("The CUPS queue for '%s' is in a stopped or busy state (%d). Please check the queue and try again." % (printer_name, printer_state))
404                             sys.exit(1)
405
406                     cups.resetOptions()
407
408                     #
409                     # Wait for fax to finish rendering
410                     #
411
412                     end_time = time.time() + 120.0
413                     while time.time() < end_time:
414                         log.debug("Waiting for fax...")
415                         try:
416                             result = list(service.CheckForWaitingFax(device_uri, prop.username, sent_job_id))
417                             log.debug(repr(result))
418
419                         except dbus.exceptions.DBusException:
420                             log.error("Cannot communicate with hp-systray. Canceling...")
421                             cups.cancelJob(sent_job_id)
422                             sys.exit(1)
423
424                         fax_file = str(result[7])
425                         log.info(fax_file)
426
427                         if fax_file:
428                             log.debug("Fax file=%s" % fax_file)
429                             title = str(result[5])
430                             break
431
432                         time.sleep(1)
433
434                     else:
435                         log.error("Timeout waiting for rendering. Canceling job #%d..." % sent_job_id)
436                         cups.cancelJob(sent_job_id)
437                         sys.exit(1)
438
439                     # open the rendered file to read the file header
440                     f = file(fax_file, 'r')
441                     header = f.read(fax.FILE_HEADER_SIZE)
442
443                     if len(header) != fax.FILE_HEADER_SIZE:
444                         log.error("Invalid fax file! (truncated header or no data)")
445                         sys.exit(1)
446
447                     mg, version, total_pages, hort_dpi, vert_dpi, page_size, \
448                         resolution, encoding, reserved1, reserved2 = \
449                         struct.unpack(">8sBIHHBBBII", header[:fax.FILE_HEADER_SIZE])
450
451                     log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
452                               (mg, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding))
453
454                     file_list.append((fax_file, mime_type, "", title, total_pages))
455                     f.close()
456
457             #
458             # Insure that the device is in an OK state
459             #
460
461             dev = None
462
463             log.debug("\nChecking device state...")
464             try:
465                 dev = fax.getFaxDevice(device_uri, printer_name)
466
467                 try:
468                     dev.open()
469                 except Error, e:
470                     log.warn(e.msg)
471
472                 try:
473                     dev.queryDevice(quick=True)
474                 except Error, e:
475                     log.error("Query device error (%s)." % e.msg)
476                     dev.error_state = ERROR_STATE_ERROR
477
478                 if dev.error_state > ERROR_STATE_MAX_OK and \
479                     dev.error_state not in (ERROR_STATE_LOW_SUPPLIES, ERROR_STATE_LOW_PAPER):
480
481                     log.error("Device is busy or in an error state (code=%d). Please wait for the device to become idle or clear the error and try again." % dev.error_state)
482                     sys.exit(1)
483
484                 user_conf.set('last_used', 'device_uri', dev.device_uri)
485
486                 log.debug("File list:")
487
488                 for f in file_list:
489                     log.debug(str(f))
490
491                 service.SendEvent(device_uri, printer_name, EVENT_START_FAX_JOB, prop.username, 0, '')
492
493                 update_queue = Queue.Queue()
494                 event_queue = Queue.Queue()
495
496                 log.info("\nSending fax...")
497
498                 if not dev.sendFaxes(phone_num_list, file_list, "",
499                                      "", None, False, printer_name,
500                                      update_queue, event_queue):
501
502                     log.error("Send fax is active. Please wait for operation to complete.")
503                     service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_FAIL, prop.username, 0, '')
504                     sys.exit(1)
505
506                 try:
507                     cont = True
508                     while cont:
509                         while update_queue.qsize():
510                             try:
511                                 status, page_num, phone_num = update_queue.get(0)
512                             except Queue.Empty:
513                                 break
514
515                             if status == fax.STATUS_IDLE:
516                                 log.debug("Idle")
517
518                             elif status == fax.STATUS_PROCESSING_FILES:
519                                 log.info("\nProcessing page %d" % page_num)
520
521                             elif status == fax.STATUS_DIALING:
522                                 log.info("\nDialing %s..." % phone_num)
523
524                             elif status == fax.STATUS_CONNECTING:
525                                 log.info("\nConnecting to %s..." % phone_num)
526
527                             elif status == fax.STATUS_SENDING:
528                                 log.info("\nSending page %d to %s..." % (page_num, phone_num))
529
530                             elif status == fax.STATUS_CLEANUP:
531                                 log.info("\nCleaning up...")
532
533                             elif status in (fax.STATUS_ERROR, fax.STATUS_BUSY, fax.STATUS_COMPLETED):
534                                 cont = False
535
536                                 if status  == fax.STATUS_ERROR:
537                                     log.error("Fax send error.")
538                                     service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_FAIL, prop.username, 0, '')
539
540                                 elif status == fax.STATUS_BUSY:
541                                     log.error("Fax device is busy. Please try again later.")
542                                     service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_FAIL, prop.username, 0, '')
543
544                                 elif status == fax.STATUS_COMPLETED:
545                                     log.info("\nCompleted successfully.")
546                                     service.SendEvent(device_uri, printer_name, EVENT_END_FAX_JOB, prop.username, 0, '')
547
548                         update_spinner()
549                         time.sleep(2)
550
551                     cleanup_spinner()
552
553                 except KeyboardInterrupt:
554                     event_queue.put((fax.EVENT_FAX_SEND_CANCELED, '', '', ''))
555                     service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_CANCELED, prop.username, 0, '')
556                     log.error("Cancelling...")
557
558             finally:
559                 log.debug("Waiting for send fax thread to exit...")
560                 if dev is not None:
561                     dev.waitForSendFaxThread()
562                     log.debug("Closing device...")
563                     dev.close()
564
565         finally:
566             mod.unlockInstance()
567
568     except KeyboardInterrupt:
569         log.error("User exit")
570
571 log.info("")
572 log.info("Done.")