1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """\
22 Print jobs can either be sent to any of the local print queues (CUPS, Win32API),
23 be opened in an external PDF viewer, be saved to a local folder or be handed
24 over to a custom (print) command. This is defined by four print action classes
25 (L{X2goPrintActionDIALOG}, L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and
26 L{X2goPrintActionPRINTCMD}).
27
28 """
29 __NAME__ = 'x2goprintactions-pylib'
30
31
32 import os
33 import sys
34 import shutil
35 import copy
36 import types
37 import threading
38 import time
39 import re
40 import string
41
42 from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
43 if _X2GOCLIENT_OS in ("Windows"):
44 import subprocess
45 import win32api
46 import win32print
47 else:
48 import gevent_subprocess as subprocess
49
50
51 import log
52 import defaults
53
54 import utils
55 import x2go_exceptions
56
57 _PRINT_ENV = os.environ.copy()
61
62 __name__ = 'NAME'
63 __description__ = 'DESCRIPTION'
64
66 """\
67 This is a meta class and has no functionality as such. It is used as parent
68 class by »real« X2go print actions.
69
70 @param client_instance: the underlying L{X2goClient} instance
71 @type client_instance: C{instance}
72 @param logger: you can pass an L{X2goLogger} object to the
73 L{X2goPrintAction} constructor
74 @type logger: C{instance}
75 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
76 constructed with the given loglevel
77 @type loglevel: C{int}
78
79 """
80 if logger is None:
81 self.logger = log.X2goLogger(loglevel=loglevel)
82 else:
83 self.logger = copy.deepcopy(logger)
84 self.logger.tag = __NAME__
85
86
87 self.profile_name = 'UNKNOWN'
88 self.session_name = 'UNKNOWN'
89
90 self.client_instance = client_instance
91
92 @property
94 """\
95 Return the X2go print action's name.
96
97 """
98 return self.__name__
99
100 @property
102 """\
103 Return the X2go print action's description text.
104
105 """
106 return self.__description__
107
108 - def do_print(self, pdf_file, job_title, spool_dir, ):
109 """\
110 Perform the defined print action (doing nothing in L{X2goPrintAction} parent class).
111
112 @param pdf_file: PDF file name as placed in to the X2go spool directory
113 @type pdf_file: C{str}
114 @param job_title: human readable print job title
115 @type job_title: C{str}
116 @param spool_dir: location of the X2go client's spool directory
117 @type spool_dir: C{str}
118
119 """
120 pass
121
123 """\
124 Extract a human readable filename for the X2go print job file.
125
126 @param pdf_file: PDF file name as placed in to the X2go spool directory
127 @type pdf_file: C{str}
128 @param job_title: human readable print job title
129 @type job_title: C{str}
130 @param target_path: target path for human readable file
131 @type target_path: C{str}
132 @return: full readable file name path
133 @rtype: C{str}
134
135 """
136 _hr_path = os.path.expanduser(os.path.join(target_path, '%s.pdf' % utils.slugify(job_title)))
137 i = 0
138
139 while os.path.exists(_hr_path):
140 i += 1
141 _hr_path = os.path.expanduser(os.path.join(target_path, '%s(%s).pdf' % (utils.slugify(job_title), i)))
142
143 return _hr_path
144
147 """\
148 Print action that views incoming print job in an external PDF viewer application.
149
150 """
151 __name__= 'PDFVIEW'
152 __decription__= 'View as PDF document'
153
154 pdfview_cmd = None
155
157 """\
158 @param client_instance: the underlying L{X2goClient} instance
159 @type client_instance: C{instance}
160 @param pdfview_cmd: command that starts the external PDF viewer application
161 @type pdfview_cmd: C{str}
162 @param logger: you can pass an L{X2goLogger} object to the
163 L{X2goPrintActionPDFVIEW} constructor
164 @type logger: C{instance}
165 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
166 constructed with the given loglevel
167 @type loglevel: C{int}
168
169 """
170 if pdfview_cmd is None:
171 pdfview_cmd = defaults.DEFAULT_PDFVIEW_CMD
172 self.pdfview_cmd = pdfview_cmd
173 X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel)
174
175 - def do_print(self, pdf_file, job_title, spool_dir, ):
176 """\
177 Open an incoming X2go print job (PDF file) in an external PDF viewer application.
178
179 @param pdf_file: PDF file name as placed in to the X2go spool directory
180 @type pdf_file: C{str}
181 @param job_title: human readable print job title
182 @type job_title: C{str}
183 @param spool_dir: location of the X2go client's spool directory
184 @type spool_dir: C{str}
185
186 """
187 if _X2GOCLIENT_OS == "Windows":
188 self.logger('viewing incoming job in PDF viewer with Python\'s os.startfile( command): %s' % pdf_file, loglevel=log.loglevel_DEBUG)
189 try:
190 os.startfile(pdf_file)
191 except WindowsError, win_err:
192 if self.client_instance:
193 self.client_instance.HOOK_printaction_error(pdf_file,
194 profile_name=self.profile_name,
195 session_name=self.session_name,
196 err_msg=str(win_err)
197 )
198 else:
199 self.logger('Encountered WindowsError: %s' % str(win_err), loglevel=log.loglevel_ERROR)
200 time.sleep(20)
201 else:
202 _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir, )
203 shutil.copy2(pdf_file, _hr_filename)
204 cmd_line = [ self.pdfview_cmd, _hr_filename, ]
205 self.logger('viewing incoming PDF with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG)
206 try:
207 p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV)
208 except OSError, e:
209 if e.errno == 2:
210 cmd_line = [ defaults.DEFAULT_PDFVIEW_CMD, _hr_filename ]
211 p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV)
212 else:
213 raise(e)
214 self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG)
215 time.sleep(20)
216 os.remove(_hr_filename)
217
220 """\
221 Print action that saves incoming print jobs to a local folder.
222
223 """
224 __name__ = 'PDFSAVE'
225 __decription__= 'Save as PDF'
226
227 save_to_folder = None
228
256
257 - def do_print(self, pdf_file, job_title, spool_dir):
258 """\
259 Save an incoming X2go print job (PDF file) to a local folder.
260
261 @param pdf_file: PDF file name as placed in to the X2go spool directory
262 @type pdf_file: C{str}
263 @param job_title: human readable print job title
264 @type job_title: C{str}
265 @param spool_dir: location of the X2go client's spool directory
266 @type spool_dir: C{str}
267
268 """
269 dest_file = self._humanreadable_filename(pdf_file, job_title, target_path=self.save_to_folder)
270 shutil.copy2(pdf_file, dest_file)
271
274 """\
275 Print action that actually prints an incoming print job file.
276
277 """
278 __name__ = 'PRINT'
279 __decription__= 'UNIX/Win32GDI printing'
280
282 """\
283 @param client_instance: the underlying L{X2goClient} instance
284 @type client_instance: C{instance}
285 @param printer: name of the preferred printer, if C{None} the system's/user's default printer will be used
286 @type printer: C{str}
287 @param logger: you can pass an L{X2goLogger} object to the
288 L{X2goPrintActionPRINT} constructor
289 @type logger: C{instance}
290 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
291 constructed with the given loglevel
292 @type loglevel: C{int}
293
294 """
295 self.printer = printer
296 X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel)
297
298 - def do_print(self, pdf_file, job_title, spool_dir, ):
299 """\
300 Actually really print an incoming X2go print job (PDF file) to a local printer device.
301
302 @param pdf_file: PDF file name as placed in to the X2go spool directory
303 @type pdf_file: C{str}
304 @param job_title: human readable print job title
305 @type job_title: C{str}
306 @param spool_dir: location of the X2go client's spool directory
307 @type spool_dir: C{str}
308
309 """
310 _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir)
311 if _X2GOCLIENT_OS == 'Windows':
312 _default_printer = win32print.GetDefaultPrinter()
313 if self.printer:
314 _printer = self.printer
315 win32print.SetDefaultPrinter(_printer)
316 else:
317 _printer = _default_printer
318 self.logger('printing incoming PDF file %s' % pdf_file, loglevel=log.loglevel_NOTICE)
319 self.logger('printer name is ,,%s\'\'' % _printer, loglevel=log.loglevel_DEBUG)
320 try:
321 _stdin = file('nul', 'r')
322 _shell = True
323 if self.client_instance:
324 _gsprint_bin = self.client_instance.client_printing.get_value('print', 'gsprint')
325 self.logger('Using gsprint.exe path from printing config file: %s' % _gsprint_bin, loglevel=log.loglevel_DEBUG)
326 else:
327 _program_files = os.environ['ProgramFiles']
328 _gsprint_bin = os.path.normpath(os.path.join(_program_files, 'ghostgum', 'gsview', 'gsprint.exe',))
329 self.logger('Using hard-coded gsprint.exe path: %s' % _gsprint_bin, loglevel=log.loglevel_DEBUG)
330 self.logger('Trying Ghostgum tool ,,gsprint.exe'' for printing first (full path: %s)' % _gsprint_bin, loglevel=log.loglevel_DEBUG)
331 p = subprocess.Popen([_gsprint_bin, pdf_file, ],
332 stdin=_stdin,
333 stdout=subprocess.PIPE,
334 stderr=subprocess.STDOUT,
335 shell=_shell,
336 )
337
338 time.sleep(10)
339
340 except:
341 self.logger('Falling back to win32api printing...', loglevel=log.loglevel_DEBUG)
342 try:
343 win32api.ShellExecute (
344 0,
345 "print",
346 pdf_file,
347 None,
348 ".",
349 0
350 )
351
352 time.sleep(10)
353 except win32api.error, e:
354 if self.client_instance:
355 self.client_instance.HOOK_printaction_error(filename=_hr_filename, printer=_printer, err_msg=e.message, profile_name=self.profile_name, session_name=self.session_name)
356 else:
357 self.logger('Encountered win32api.error: %s' % str(e), loglevel=log.loglevel_ERROR)
358
359 if self.printer:
360 win32print.SetDefaultPrinter(_default_printer)
361 time.sleep(60)
362
363 else:
364 _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir)
365 self.logger('printing incoming PDF file %s' % _hr_filename, loglevel=log.loglevel_NOTICE)
366 if self.printer:
367 self.logger('printer name is %s' % self.printer, loglevel=log.loglevel_DEBUG)
368 else:
369 self.logger('using default CUPS printer', loglevel=log.loglevel_DEBUG)
370 shutil.copy2(pdf_file, _hr_filename)
371 if self.printer is None:
372 cmd_line = [ 'lpr',
373 '-h',
374 '-r',
375 '-J%s' % job_title,
376 '%s' % _hr_filename,
377 ]
378 else:
379 cmd_line = [ 'lpr',
380 '-h',
381 '-r',
382 '-P%s' % self.printer,
383 '-J%s' % job_title,
384 '%s' % _hr_filename,
385 ]
386 self.logger('executing local print command: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG)
387 p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV)
388
389
390 self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG)
391 time.sleep(20)
392 try: os.remove(_hr_filename)
393 except OSError: pass
394
397 """\
398 Print action that calls an external command for further processing of incoming print jobs.
399
400 The print job's PDF filename will be prepended as last argument to the print command
401 used in L{X2goPrintActionPRINTCMD} instances.
402
403 """
404 __name__ = 'PRINTCMD'
405 __decription__= 'Print via a command (like LPR)'
406
408 """\
409 @param client_instance: the underlying L{X2goClient} instance
410 @type client_instance: C{instance}
411 @param print_cmd: external command to be called on incoming print jobs
412 @type print_cmd: C{str}
413 @param logger: you can pass an L{X2goLogger} object to the
414 L{X2goPrintActionPRINTCMD} constructor
415 @type logger: C{instance}
416 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
417 constructed with the given loglevel
418 @type loglevel: C{int}
419
420 """
421 if print_cmd is None:
422 print_cmd = defaults.DEFAULT_PRINTCMD_CMD
423 self.print_cmd = print_cmd
424 X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel)
425
426 - def do_print(self, pdf_file, job_title, spool_dir):
427 """\
428 Execute an external command that has been defined on construction
429 of this L{X2goPrintActionPRINTCMD} instance.
430
431 @param pdf_file: PDF file name as placed in to the X2go spool directory
432 @type pdf_file: C{str}
433 @param job_title: human readable print job title
434 @type job_title: C{str}
435 @param spool_dir: location of the X2go client's spool directory
436 @type spool_dir: C{str}
437
438 """
439 _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir)
440 shutil.copy2(pdf_file, _hr_filename)
441 self.logger('executing external command ,,%s\'\' on PDF file %s' % (self.print_cmd, _hr_filename), loglevel=log.loglevel_NOTICE)
442 cmd_line = self.print_cmd.split()
443 cmd_line.append(_hr_filename)
444 self.logger('executing external command: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG)
445 p = subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV)
446
447
448 self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG)
449 time.sleep(20)
450 try: os.remove(_hr_filename)
451 except OSError: pass
452
455 """\
456 Print action that mediates opening a print dialog window. This class is rather empty,
457 the actual print dialog box must be implemented in our GUI application (with the application's
458 L{X2goClient} instance.
459
460 """
461 __name__ = 'DIALOG'
462 __decription__= 'Open a print dialog box'
463
465 """\
466 @param client_instance: an L{X2goClient} instance, within your customized L{X2goClient} make sure
467 you have a C{HOOK_open_print_dialog(filename=<str>)} method defined that will actually
468 open the print dialog.
469 @type client_instance: C{instance}
470 @param logger: you can pass an L{X2goLogger} object to the
471 L{X2goPrintActionDIALOG} constructor
472 @type logger: C{instance}
473 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
474 constructed with the given loglevel
475 @type loglevel: C{int}
476
477 """
478 if client_instance is None:
479 raise x2go_exceptions.X2goPrintActionException('the DIALOG print action needs to know the X2goClient instance (client=<instance>)')
480 X2goPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel)
481
482 - def do_print(self, pdf_file, job_title, spool_dir):
483 """\
484 Execute an external command that has been defined on construction
485 of this L{X2goPrintActionPRINTCMD} instance.
486
487 @param pdf_file: PDF file name as placed in to the X2go spool directory
488 @type pdf_file: C{str}
489 @param job_title: human readable print job title
490 @type job_title: C{str}
491 @param spool_dir: location of the X2go client's spool directory
492 @type spool_dir: C{str}
493
494 """
495 self.logger('Session %s (%s) is calling X2goClient class hook method <client_instance>.HOOK_open_print_dialog' % (self.session_name, self.profile_name), loglevel=log.loglevel_NOTICE)
496 _new_print_action = self.client_instance.HOOK_open_print_dialog(profile_name=self.profile_name, session_name=self.session_name)
497 if type(_new_print_action) != type(self):
498 _new_print_action.do_print(pdf_file, job_title, spool_dir)
499