Package x2go :: Module printactions
[frames] | no frames]

Source Code for Module x2go.printactions

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  # Copyright (C) 2010-2011 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  5  # 
  6  # Python X2go 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 3 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # Python X2go 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 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 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  # modules 
 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  # Python X2go modules 
 51  import log 
 52  import defaults 
 53  # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) 
 54  import utils 
 55  import x2go_exceptions 
 56   
 57  _PRINT_ENV = os.environ.copy() 
58 59 60 -class X2goPrintAction(object):
61 62 __name__ = 'NAME' 63 __description__ = 'DESCRIPTION' 64
65 - def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # these get set from within the X2goPrintQueue class 87 self.profile_name = 'UNKNOWN' 88 self.session_name = 'UNKNOWN' 89 90 self.client_instance = client_instance
91 92 @property
93 - def name():
94 """\ 95 Return the X2go print action's name. 96 97 """ 98 return self.__name__
99 100 @property
101 - def description():
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
122 - def _humanreadable_filename(self, pdf_file, job_title, target_path):
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
145 146 -class X2goPrintActionPDFVIEW(X2goPrintAction):
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
156 - def __init__(self, client_instance=None, pdfview_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT):
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
218 219 -class X2goPrintActionPDFSAVE(X2goPrintAction):
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
229 - def __init__(self, client_instance=None, save_to_folder=None, logger=None, loglevel=log.loglevel_DEFAULT):
230 """\ 231 @param client_instance: the underlying L{X2goClient} instance 232 @type client_instance: C{instance} 233 @param save_to_folder: saving location for incoming print jobs (PDF files) 234 @type save_to_folder: C{str} 235 @param logger: you can pass an L{X2goLogger} object to the 236 L{X2goPrintActionPDFSAVE} constructor 237 @type logger: C{instance} 238 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 239 constructed with the given loglevel 240 @type loglevel: C{int} 241 242 """ 243 if save_to_folder is None: 244 save_to_folder = defaults.DEFAULT_PDFSAVE_LOCATION 245 if not utils.is_abs_path(save_to_folder): 246 if not save_to_folder.startswith('~'): 247 save_to_folder = os.path.normpath('~/%s' % save_to_folder) 248 save_to_folder = os.path.expanduser(save_to_folder) 249 self.save_to_folder = save_to_folder 250 251 X2goPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) 252 253 self.logger('Save location for incoming PDFs is: %s' % self.save_to_folder, loglevel=log.loglevel_DEBUG) 254 if not os.path.exists(self.save_to_folder): 255 os.makedirs(self.save_to_folder, mode=0755)
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
272 273 -class X2goPrintActionPRINT(X2goPrintAction):
274 """\ 275 Print action that actually prints an incoming print job file. 276 277 """ 278 __name__ = 'PRINT' 279 __decription__= 'UNIX/Win32GDI printing' 280
281 - def __init__(self, client_instance=None, printer=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # give gsprint.exe a little time to find our printer 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 # give the win32api some time to find our printer... 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 # this is nasty!!!! 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
395 396 -class X2goPrintActionPRINTCMD(X2goPrintAction):
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
407 - def __init__(self, client_instance=None, print_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # this is nasty!!!! 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
453 454 -class X2goPrintActionDIALOG(X2goPrintAction):
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
464 - def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
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