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