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