Tizen 2.1 base
[platform/upstream/hplip.git] / fax / fax.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2010 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # Author: Don Welch
20 #
21
22 from __future__ import generators
23
24 # Std Lib
25 import sys
26 import os
27 import threading
28 import cPickle
29 import time
30 from cStringIO import StringIO
31 import struct
32
33 # Local
34 from base.g import *
35 from base.codes import *
36 from base.ldif import LDIFParser
37 from base import device, utils, vcard
38 from prnt import cups
39
40 try:
41     import coverpages
42 except ImportError:
43     pass
44
45 try:
46     import dbus
47 except ImportError:
48     log.error("dbus is required for PC send fax.")
49
50 import warnings
51 # Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters
52 # (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04)
53 warnings.simplefilter("ignore", DeprecationWarning)
54
55
56 # Update queue values (Send thread ==> UI)
57 STATUS_IDLE = 0
58 STATUS_PROCESSING_FILES = 1
59 STATUS_SENDING_TO_RECIPIENT = 2
60 STATUS_DIALING = 3
61 STATUS_CONNECTING = 4
62 STATUS_SENDING = 5
63 STATUS_COMPLETED = 6
64 STATUS_CREATING_COVER_PAGE = 7
65 STATUS_ERROR = 8
66 STATUS_BUSY = 9
67 STATUS_CLEANUP = 10
68 STATUS_ERROR_IN_CONNECTING = 11
69 STATUS_ERROR_IN_TRANSMITTING = 12
70 STATUS_ERROR_PROBLEM_IN_FAXLINE = 13
71 STATUS_JOB_CANCEL = 14 
72
73 # Event queue values (UI ==> Send thread)
74 EVENT_FAX_SEND_CANCELED = 1
75 # Other values in queue are:
76 #EVENT_FAX_RENDER_COMPLETE_BEGIN = 8010
77 #EVENT_FAX_RENDER_COMPLETE_SENDDATA = 8011
78 #EVENT_FAX_RENDER_COMPLETE_END = 8012
79
80 # **************************************************************************** #
81 # HPLIP G3 Fax File Format (big endian)
82 #
83 # #==============================================#
84 # # File Header: Total 28 bytes                  #
85 # #..............................................#
86 # # Magic bytes: 8 bytes ("hplip_g3")            #
87 # # Format version: 8 bits (1)                   #
88 # # Total pages in file(=p): 32 bits             #
89 # # Hort DPI: 16 bits (200 or 300)               #
90 # # Vert DPI: 16 bits (100, 200, or 300)         #
91 # # Page Size: 8 bits (0=Unk, 1=Letter, 2=A4,    #
92 # #                    3=Legal)                  #
93 # # Resolution: 8 bits (0=Unk, 1=Std, 2=Fine,    #
94 # #                     3=300DPI)                #
95 # # Encoding: 8 bits (2=MH, 4=MMR, 7=JPEG)       #
96 # # Reserved1: 32 bits (0)                       #
97 # # Reserved2: 32 bits (0)                       #
98 # #----------------------------------------------#
99 # # Page 1 Header: Total 24 bytes                #
100 # #..............................................#
101 # # Page number: 32 bits (1 based)               #
102 # # Pixels per row: 32 bits                      #
103 # # Rows this page: 32 bits                      #
104 # # Image bytes this page(=x): 32 bits           #
105 # # Thumbnail bytes this page(=y): 32 bits       #
106 # #  (thumbnail not present if y == 0)           #
107 # #  (encoding?)                                 #
108 # #     letter: 134 px wide x 173 px high        #
109 # #     legal:  134 px wide x 221 px high        #
110 # #     a4 :    134 px wide x 190 px high        #
111 # # Reserved3: 32 bits (0)                       #
112 # #..............................................#
113 # # Image data: x bytes                          #
114 # #..............................................#
115 # # Thumbnail data: y bytes (if present)         #
116 # #----------------------------------------------#
117 # # Page 2 Header: Total 24 bytes                #
118 # #..............................................#
119 # # Image Data                                   #
120 # #..............................................#
121 # # Thumbnail data (if present)                  #
122 # #----------------------------------------------#
123 # # ... Pages 3 - (p-1) ...                      #
124 # #----------------------------------------------#
125 # # Page p Header: Total 24 bytes                #
126 # #..............................................#
127 # # Image Data                                   #
128 # #..............................................#
129 # # Thumbnail data (if present)                  #
130 # #==============================================#
131 #
132
133 RESOLUTION_STD = 1
134 RESOLUTION_FINE = 2
135 RESOLUTION_300DPI = 3
136
137 FILE_HEADER_SIZE = 28
138 PAGE_HEADER_SIZE = 24
139 # **************************************************************************** #
140
141 ##skip_dn = ["uid=foo,ou=People,dc=example,dc=com",
142 ##    "uid=bar,ou=People,dc=example,dc=com", "dc=example,dc=com"]
143
144 class FaxLDIFParser(LDIFParser):
145     def __init__(self, input, db):
146         LDIFParser.__init__(self, input)
147         self.db = db
148
149     def handle(self, dn, entry):
150         if dn:
151             try:
152                 firstname = entry['givenName'][0]
153             except KeyError:
154                 try:
155                     firstname = entry['givenname'][0]
156                 except KeyError:
157                     firstname = ''
158
159             try:
160                 lastname = entry['sn'][0]
161             except KeyError:
162                 lastname = ''
163
164             try:
165                 nickname = entry['cn'][0]
166             except KeyError:
167                 nickname = firstname + ' ' + lastname
168
169             try:
170                 fax = entry['facsimiletelephonenumber'][0] # fax
171             except KeyError:
172                 try:
173                     fax = entry['fax'][0]
174                 except KeyError:
175                     fax  = ''
176
177             grps = []
178             try:
179                 grps = entry['ou']
180             except KeyError:
181                 pass
182
183             grps.append(u'All')
184             groups = [g for g in grps if g]
185
186             if nickname:
187                 log.debug("Import: name=%s, fax=%s, group(s)=%s, notes=%s" % ( nickname, fax, ','.join(groups), dn))
188                 self.db.set(nickname, title, firstname, lastname, fax, groups, dn)
189
190
191
192 # **************************************************************************** #
193 class FaxAddressBook(object): # Pickle based address book
194     def __init__(self):
195         self._data = {}
196         #
197         # { 'name' : {'name': u'',
198         #             'firstname' : u'', # NOT USED STARTING IN 2.8.9
199         #             'lastname': u', # NOT USED STARTING IN 2.8.9
200         #             'title' : u'',  # NOT USED STARTING IN 2.8.9
201         #             'fax': u'',
202         #             'groups' : [u'', u'', ...],
203         #             'notes' : u'', } ...
204         # }
205         #
206         self.load()
207
208     def load(self):
209         self._fab = "/dev/null"
210         if prop.user_dir != None:
211             self._fab = os.path.join(prop.user_dir, "fab.pickle")
212             #old_fab = os.path.join(prop.user_dir, "fab.db")
213
214             # Load the existing pickle if present
215             if os.path.exists(self._fab):
216                pickle_file = open(self._fab, "r")
217                self._data = cPickle.load(pickle_file)
218                pickle_file.close()
219             else:
220                self.save() # save the empty file to create the file
221
222
223     def set(self, name, title, firstname, lastname, fax, groups, notes):
224         try:
225             grps = [unicode(s) for s in groups]
226         except UnicodeDecodeError:
227             grps = [unicode(s.decode('utf-8')) for s in groups]
228
229         self._data[unicode(name)] = {'name' : unicode(name),
230                                     'title': unicode(title),  # NOT USED STARTING IN 2.8.9
231                                     'firstname': unicode(firstname), # NOT USED STARTING IN 2.8.9
232                                     'lastname': unicode(lastname), # NOT USED STARTING IN 2.8.9
233                                     'fax': unicode(fax),
234                                     'notes': unicode(notes),
235                                     'groups': grps}
236
237         self.save()
238
239     insert = set
240
241
242     def set_key_value(self, name, key, value):
243         self._data[unicode(name)][key] = value
244         self.save()
245
246
247     def get(self, name):
248         return self._data.get(name, None)
249
250     select = get
251
252     def rename(self, old_name, new_name):
253         try:
254             self._data[old_name]
255         except KeyError:
256             return
257         else:
258             try:
259                 self._data[new_name]
260             except KeyError:
261                 self._data[new_name] = self._data[old_name].copy()
262                 self._data[new_name]['name'] = new_name
263                 del self._data[old_name]
264                 self.save()
265
266
267     def get_all_groups(self):
268         all_groups = []
269         for e, v in self._data.items():
270             for g in v['groups']:
271                 if g not in all_groups:
272                     all_groups.append(g)
273         return all_groups
274
275
276     def get_all_records(self):
277         return self._data
278
279
280     def get_all_names(self):
281         return self._data.keys()
282
283
284     def save(self):
285         try:
286             pickle_file = open(self._fab, "w")
287             cPickle.dump(self._data, pickle_file, cPickle.HIGHEST_PROTOCOL)
288             pickle_file.close()
289         except IOError:
290             log.error("I/O error saving fab file.")
291
292
293     def clear(self):
294         self._data = {}
295         self.save()
296
297
298     def delete(self, name):
299         if name in self._data:
300             del self._data[name]
301             self.save()
302             return True
303
304         return False
305
306
307     def last_modification_time(self):
308         try:
309             return os.stat(self._fab).st_mtime
310         except OSError:
311             return 0
312
313
314     def update_groups(self, group, members):
315         for e, v in self._data.items():
316             if v['name'] in members: # membership indicated
317                 if not group in v['groups']:
318                     v['groups'].append(unicode(group))
319             else:
320                 if group in v['groups']:
321                     v['groups'].remove(unicode(group))
322         self.save()
323
324
325     def delete_group(self, group):
326         for e, v in self._data.items():
327             if group in v['groups']:
328                 v['groups'].remove(unicode(group))
329         self.save()
330
331
332     def group_members(self, group):
333         members = []
334         for e, v in self._data.items():
335             if group in v['groups']:
336                 members.append(e)
337         return members
338
339
340     def add_to_group(self, group, members):
341         group_members = self.group_members(group)
342         new_group_members = []
343         for m in members:
344             if m not in group_members:
345                 new_group_members.append(m)
346
347         self.update_groups(group, group_members + new_group_members)
348
349
350     def remove_from_group(self, group, remove_members):
351         group_members = self.group_members(group)
352         new_group_members = []
353         for m in group_members:
354             if m not in remove_members:
355                 new_group_members.append(m)
356
357         self.update_groups(group, new_group_members)
358
359
360     def rename_group(self, old_group, new_group):
361         members = self.group_members(old_group)
362         self.update_groups(old_group, [])
363         self.update_groups(new_group, members)
364
365
366     def import_ldif(self, filename):
367         try:
368             data = open(filename, 'r').read()
369             log.debug_block(filename, data)
370             parser = FaxLDIFParser(open(filename, 'r'), self)
371             parser.parse()
372             self.save()
373             return True, ''
374         except ValueError, e:
375             return False, e.message
376
377
378     def import_vcard(self, filename):
379         data = file(filename, 'r').read()
380         log.debug_block(filename, data)
381
382         for card in vcard.VCards(vcard.VFile(vcard.opentextfile(filename))):
383             log.debug(card)
384
385             if card['name']:
386                 fax = ''
387                 for x in range(1, 9999):
388                     if x == 1:
389                         s = 'phone'
390                     else:
391                         s = 'phone%d' % x
392
393                     try:
394                         card[s]
395                     except KeyError:
396                         break
397                     else:
398                         if 'fax' in card[s]['type']:
399                             fax = card[s]['number']
400                             break
401
402                 org = card.get('organisation', '')
403                 if org:
404                     org = [org]
405                 else:
406                     org = card.get('categories', '').split(';')
407                     if not org:
408                         org = []
409
410                 org.append(u'All')
411                 groups = [o for o in org if o]
412
413                 name = card['name']
414                 notes = card.get('notes', u'')
415                 log.debug("Import: name=%s, fax=%s group(s)=%s notes=%s" % (name, fax, ','.join(groups), notes))
416                 self.set(name, u'', u'', u'', fax, groups, notes)
417
418         return True, ''
419
420
421 # **************************************************************************** #
422 class FaxDevice(device.Device):
423
424     def __init__(self, device_uri=None, printer_name=None,
425                  callback=None,
426                  fax_type=FAX_TYPE_NONE,
427                  disable_dbus=False):
428
429         device.Device.__init__(self, device_uri, printer_name,
430                                None, callback, disable_dbus)
431
432         self.send_fax_thread = None
433         self.upload_log_thread = None
434         self.fax_type = fax_type
435
436         if not disable_dbus:
437             session_bus = dbus.SessionBus()
438             self.service = session_bus.get_object('com.hplip.StatusService', "/com/hplip/StatusService")
439         else:
440             self.service = None
441
442
443     def setPhoneNum(self, num):
444         raise AttributeError
445
446     def getPhoneNum(self):
447         raise AttributeError
448
449     phone_num = property(getPhoneNum, setPhoneNum)
450
451
452     def setStationName(self, name):
453         raise AttributeError
454
455     def getStationName(self):
456         raise AttributeError
457
458     station_name = property(getStationName, setStationName)
459
460     def setDateAndTime(self):
461         raise AttributeError
462
463     def uploadLog(self):
464         raise AttributeError
465
466     def isUploadLogActive(self):
467         raise AttributeError
468
469     def waitForUploadLogThread(self):
470         raise AttributeError
471
472     def sendFaxes(self, phone_num_list, fax_file_list, cover_message='', cover_re='',
473                   cover_func=None, preserve_formatting=False, printer_name='',
474                   update_queue=None, event_queue=None):
475
476         raise AttributeError
477
478     def isSendFaxActive(self):
479         if self.send_fax_thread is not None:
480             return self.send_fax_thread.isAlive()
481         else:
482             return False
483
484     def waitForSendFaxThread(self):
485         if self.send_fax_thread is not None and \
486             self.send_fax_thread.isAlive():
487
488             try:
489                 self.send_fax_thread.join()
490             except KeyboardInterrupt:
491                 pass
492
493
494 # **************************************************************************** #
495
496
497 def getFaxDevice(device_uri=None, printer_name=None,
498                  callback=None,
499                  fax_type=FAX_TYPE_NONE,
500                  disable_dbus=False):
501
502     if fax_type == FAX_TYPE_NONE:
503         if device_uri is None and printer_name is not None:
504             printers = cups.getPrinters()
505
506             for p in printers:
507                 if p.name.lower() == printer_name.lower():
508                     device_uri = p.device_uri
509                     break
510             else:
511                 raise Error(ERROR_DEVICE_NOT_FOUND)
512
513         if device_uri is not None:
514             mq = device.queryModelByURI(device_uri)
515             fax_type = mq['fax-type']
516
517     log.debug("fax-type=%d" % fax_type)
518
519     if fax_type in (FAX_TYPE_BLACK_SEND_EARLY_OPEN, FAX_TYPE_BLACK_SEND_LATE_OPEN):
520         from pmlfax import PMLFaxDevice
521         return PMLFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
522
523     elif fax_type == FAX_TYPE_SOAP:
524         from soapfax import SOAPFaxDevice
525         return SOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
526
527     elif fax_type == FAX_TYPE_LEDMSOAP:
528         from ledmsoapfax import LEDMSOAPFaxDevice
529         return LEDMSOAPFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
530
531     elif fax_type == FAX_TYPE_MARVELL:
532         from marvellfax import MarvellFaxDevice
533         return MarvellFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
534
535     elif fax_type == FAX_TYPE_LEDM:
536         from ledmfax import LEDMFaxDevice
537         return LEDMFaxDevice(device_uri, printer_name, callback, fax_type, disable_dbus)
538
539     else:
540         raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION)
541
542 # **************************************************************************** #
543
544
545
546
547 # TODO: Define these in only 1 place!
548 STATE_DONE = 0
549 STATE_ABORTED = 10
550 STATE_SUCCESS = 20
551 STATE_BUSY = 25
552 STATE_READ_SENDER_INFO = 30
553 STATE_PRERENDER = 40
554 STATE_COUNT_PAGES = 50
555 STATE_NEXT_RECIPIENT = 60
556 STATE_COVER_PAGE = 70
557 STATE_SINGLE_FILE = 80
558 STATE_MERGE_FILES = 90
559 STATE_SINGLE_FILE = 100
560 STATE_SEND_FAX = 110
561 STATE_CLEANUP = 120
562 STATE_ERROR = 130
563
564 class FaxSendThread(threading.Thread):
565     def __init__(self, dev, service, phone_num_list, fax_file_list,
566                  cover_message='', cover_re='', cover_func=None, preserve_formatting=False,
567                  printer_name='', update_queue=None, event_queue=None):
568
569         threading.Thread.__init__(self)
570
571         self.dev = dev # device.Device
572         self.service = service # dbus proxy to status server object
573         self.phone_num_list = phone_num_list
574         self.fax_file_list = fax_file_list
575         self.update_queue = update_queue
576         self.event_queue = event_queue
577         self.cover_message = cover_message
578         self.cover_re = cover_re
579         self.cover_func = cover_func
580         self.current_printer = printer_name
581         self.stream = StringIO()
582         self.prev_update = ''
583         self.remove_temp_file = False
584         self.preserve_formatting = preserve_formatting
585         self.results = {} # {'file' : error_code,...}
586         self.cover_page_present = False
587         self.recipient_file_list = []
588         self.f = None # final file of fax data to send (pages merged)
589         self.job_hort_dpi = 0
590         self.job_hort_dpi = 0
591         self.job_vert_dpi = 0
592         self.job_page_size = 0
593         self.job_resolution = 0
594         self.job_encoding = 0
595
596
597     def pre_render(self, state):
598         # pre-render each page that needs rendering
599         # except for the cover page
600         self.cover_page_present = False
601         log.debug(self.fax_file_list)
602
603         for fax_file in self.fax_file_list: # (file, type, desc, title)
604             fax_file_name, fax_file_type, fax_file_desc, \
605                 fax_file_title, fax_file_pages = fax_file
606
607             if fax_file_type == "application/hplip-fax-coverpage": # render later
608                 self.cover_page_present = True
609                 log.debug("Skipping coverpage")
610
611             #if fax_file_type == "application/hplip-fax": # already rendered
612             else:
613                 self.rendered_file_list.append((fax_file_name, "application/hplip-fax",
614                     "HP Fax", fax_file_title))
615
616                 log.debug("Processing pre-rendered file: %s (%d pages)" %
617                     (fax_file_name, fax_file_pages))
618
619             if self.check_for_cancel():
620                 state = STATE_ABORTED
621
622         log.debug(self.rendered_file_list)
623
624         if self.check_for_cancel():
625             state = STATE_ABORTED
626
627         return state
628
629
630     def count_pages(self, state):
631         self.recipient_file_list = self.rendered_file_list[:]
632         log.debug("Counting total pages...")
633         self.job_total_pages = 0
634         log.debug(self.recipient_file_list)
635
636         i = 0
637         for fax_file in self.recipient_file_list: # (file, type, desc, title)
638             fax_file_name = fax_file[0]
639             log.debug("Processing file (counting pages): %s..." % fax_file_name)
640
641             #self.write_queue((STATUS_PROCESSING_FILES, self.job_total_pages, ''))
642
643             if os.path.exists(fax_file_name):
644                 self.results[fax_file_name] = ERROR_SUCCESS
645                 fax_file_fd = file(fax_file_name, 'r')
646                 header = fax_file_fd.read(FILE_HEADER_SIZE)
647
648                 magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
649                     resolution, encoding, reserved1, reserved2 = \
650                         self.decode_fax_header(header)
651
652                 if magic != 'hplip_g3':
653                     log.error("Invalid file header. Bad magic.")
654                     self.results[fax_file_name] = ERROR_FAX_INVALID_FAX_FILE
655                     state = STATE_ERROR
656                     continue
657
658                 if not i:
659                     self.job_hort_dpi, self.job_vert_dpi, self.job_page_size, \
660                         self.job_resolution, self.job_encoding = \
661                         hort_dpi, vert_dpi, page_size, resolution, encoding
662
663                     i += 1
664                 else:
665                     if self.job_hort_dpi != hort_dpi or \
666                         self.job_vert_dpi != vert_dpi or \
667                         self.job_page_size != page_size or \
668                         self.job_resolution != resolution or \
669                         self.job_encoding != encoding:
670
671                         log.error("Incompatible options for file: %s" % fax_file_name)
672                         self.results[fax_file_name] = ERROR_FAX_INCOMPATIBLE_OPTIONS
673                         state = STATE_ERROR
674
675
676                 log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
677                           (magic, version, total_pages, hort_dpi,
678                            vert_dpi, page_size, resolution, encoding))
679
680                 self.job_total_pages += total_pages
681
682                 fax_file_fd.close()
683
684             else:
685                 log.error("Unable to find HP Fax file: %s" % fax_file_name)
686                 self.results[fax_file_name] = ERROR_FAX_FILE_NOT_FOUND
687                 state = STATE_ERROR
688                 break
689
690             if self.check_for_cancel():
691                 state = STATE_ABORTED
692                 break
693
694
695         if self.cover_page_present:
696             self.job_total_pages += 1 # Cover pages are truncated to 1 page
697
698         log.debug("Total fax pages=%d" % self.job_total_pages)
699
700         return state
701
702     def decode_fax_header(self, header):
703         try:
704             return struct.unpack(">8sBIHHBBBII", header)
705         except struct.error:
706             return -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
707
708     def decode_page_header(self, header):
709         try:
710             return struct.unpack(">IIIIII", header)
711         except struct.error:
712             return -1, -1, -1, -1, -1, -1
713
714     def cover_page(self,  recipient):
715         if self.job_total_pages > 1:
716             state = STATE_MERGE_FILES
717         else:
718             state = STATE_SINGLE_FILE
719
720         if self.cover_page_present:
721             log.debug("Creating cover page for recipient: %s" % recipient['name'])
722             fax_file, canceled = self.render_cover_page(recipient)
723
724             if canceled:
725                 state = STATE_ABORTED
726             elif not fax_file:
727                 state = STATE_ERROR # timeout
728             else:
729                 self.recipient_file_list.insert(0, (fax_file, "application/hplip-fax",
730                                                     "HP Fax", 'Cover Page'))
731
732                 log.debug("Cover page G3 file: %s" % fax_file)
733
734                 self.results[fax_file] = ERROR_SUCCESS
735
736         return state
737
738     def single_file(self, state):
739         state = STATE_SEND_FAX
740
741         log.debug("Processing single file...")
742         self.f = self.recipient_file_list[0][0]
743
744         try:
745             f_fd = file(self.f, 'r')
746         except IOError:
747             log.error("Unable to open fax file: %s" % self.f)
748             state = STATE_ERROR
749         else:
750             header = f_fd.read(FILE_HEADER_SIZE)
751
752             magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
753                 resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)
754
755             self.results[self.f] = ERROR_SUCCESS
756
757             if magic != 'hplip_g3':
758                 log.error("Invalid file header. Bad magic.")
759                 self.results[self.f] = ERROR_FAX_INVALID_FAX_FILE
760                 state = STATE_ERROR
761
762             log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
763                       (magic, version, total_pages, hort_dpi, vert_dpi,
764                        page_size, resolution, encoding))
765
766             f_fd.close()
767
768         return state
769
770
771     def merge_files(self, state):
772         log.debug("%s State: Merge multiple files" % ("*"*20))
773         log.debug(self.recipient_file_list)
774         log.debug("Merging g3 files...")
775         self.remove_temp_file = True
776
777         if self.job_total_pages:
778             f_fd, self.f = utils.make_temp_file()
779             log.debug("Temp file=%s" % self.f)
780
781             data = struct.pack(">8sBIHHBBBII", "hplip_g3", 1L, self.job_total_pages,
782                 self.job_hort_dpi, self.job_vert_dpi, self.job_page_size,
783                 self.job_resolution, self.job_encoding,
784                 0L, 0L)
785
786             os.write(f_fd, data)
787
788             job_page_num = 1
789
790             for fax_file in self.recipient_file_list:
791                 fax_file_name = fax_file[0]
792                 log.debug("Processing file: %s..." % fax_file_name)
793
794                 if self.results[fax_file_name] == ERROR_SUCCESS:
795                     fax_file_fd = file(fax_file_name, 'r')
796                     header = fax_file_fd.read(FILE_HEADER_SIZE)
797
798                     magic, version, total_pages, hort_dpi, vert_dpi, page_size, \
799                         resolution, encoding, reserved1, reserved2 = self.decode_fax_header(header)
800
801                     if magic != 'hplip_g3':
802                         log.error("Invalid file header. Bad magic.")
803                         state = STATE_ERROR
804                         break
805
806                     log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" %
807                               (magic, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding))
808
809                     for p in range(total_pages):
810                         header = fax_file_fd.read(PAGE_HEADER_SIZE)
811
812                         page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, reserved2 = \
813                             self.decode_page_header(header)
814
815                         if page_num == -1:
816                             log.error("Page header error")
817                             state - STATE_ERROR
818                             break
819
820                         header = struct.pack(">IIIIII", job_page_num, ppr, rpp, bytes_to_read, thumbnail_bytes, 0L)
821                         os.write(f_fd, header)
822
823                         self.write_queue((STATUS_PROCESSING_FILES, job_page_num, ''))
824
825                         log.debug("Page=%d PPR=%d RPP=%d BPP=%d Thumb=%s" %
826                                   (page_num, ppr, rpp, bytes_to_read, thumbnail_bytes))
827
828                         os.write(f_fd, fax_file_fd.read(bytes_to_read))
829                         job_page_num += 1
830
831                     fax_file_fd.close()
832
833                     if self.check_for_cancel():
834                         state = STATE_ABORTED
835                         break
836
837                 else:
838                     log.error("Skipping file: %s" % fax_file_name)
839                     continue
840
841             os.close(f_fd)
842             log.debug("Total pages=%d" % self.job_total_pages)
843
844         return state
845
846
847     def next_recipient_gen(self):
848         for a in self.phone_num_list:
849             yield a
850
851     def next_file_gen(self):
852         for a in self.recipient_file_list:
853             yield a
854
855
856     def render_file(self, path, title, mime_type, force_single_page=False):
857         all_pages = True
858         page_range = ''
859         page_set = 0
860         nup = 1
861
862         cups.resetOptions()
863
864         if mime_type in ["application/x-cshell",
865                          "application/x-perl",
866                          "application/x-python",
867                          "application/x-shell",
868                          "application/x-sh",
869                          "text/plain",]:
870
871             cups.addOption('prettyprint')
872
873         if nup > 1:
874             cups.addOption('number-up=%d' % nup)
875
876         if force_single_page:
877             cups.addOption('page-ranges=1') # Force coverpage to 1 page
878
879         sent_job_id = cups.printFile(self.current_printer, path, title)
880         cups.resetOptions()
881
882         log.debug("Job ID=%d" % sent_job_id)
883         job_id = 0
884
885         time.sleep(1)
886
887         fax_file = ''
888         complete = False
889
890         end_time = time.time() + 300.0 # wait for 5 min. max
891         while time.time() < end_time:
892             log.debug("Waiting for fax...")
893
894             result = list(self.service.CheckForWaitingFax(self.dev.device_uri, prop.username, sent_job_id))
895
896             fax_file = str(result[7])
897             log.debug("Fax file=%s" % fax_file)
898
899             if fax_file:
900                 break
901
902             if self.check_for_cancel():
903                 log.error("Render canceled. Canceling job #%d..." % sent_job_id)
904                 cups.cancelJob(sent_job_id)
905                 return '', True
906
907             time.sleep(1)
908
909         else:
910             log.error("Timeout waiting for rendering. Canceling job #%d..." % sent_job_id)
911             cups.cancelJob(sent_job_id)
912             return '', False
913
914         return fax_file, False
915
916
917     def check_for_cancel(self):
918         canceled = False
919         while self.event_queue.qsize():
920             try:
921                 event = self.event_queue.get(0)
922                 if event[0] == EVENT_FAX_SEND_CANCELED:
923                     canceled = True
924                     log.debug("Cancel pressed!")
925             except Queue.Empty:
926                 break
927
928         return canceled
929
930     def render_cover_page(self, a):
931         log.debug("Creating cover page...")
932
933         pdf = self.cover_func(page_size=coverpages.PAGE_SIZE_LETTER,
934                               total_pages=self.job_total_pages,
935
936                               recipient_name=a['name'],
937                               recipient_phone='', # ???
938                               recipient_fax=a['fax'],
939
940                               sender_name=self.sender_name,
941                               sender_phone=user_conf.get('fax', 'voice_phone'),
942                               sender_fax=self.sender_fax,
943                               sender_email=user_conf.get('fax', 'email_address'),
944
945                               regarding=self.cover_re,
946                               message=self.cover_message,
947                               preserve_formatting=self.preserve_formatting)
948
949         log.debug("PDF File=%s" % pdf)
950         fax_file, canceled = self.render_file(pdf, 'Cover Page', "application/pdf",
951             force_single_page=True)
952
953         try:
954             os.remove(pdf)
955         except IOError:
956             pass
957
958         return fax_file, canceled
959
960
961     def write_queue(self, message):
962         if self.update_queue is not None and message != self.prev_update:
963             self.update_queue.put(message)
964             time.sleep(0)
965             self.prev_update = message
966
967
968     def run(self):
969         pass
970
971
972