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-2012 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 Affero 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 Affero General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU Affero 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 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  # Python X2Go modules 
 49  import log 
 50  import defaults 
 51  # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) 
 52  import utils 
 53  import x2go_exceptions 
 54   
 55  _PRINT_ENV = os.environ.copy() 
56 57 58 -class X2goPrintAction(object):
59 60 __name__ = 'NAME' 61 __description__ = 'DESCRIPTION' 62
63 - def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # these get set from within the X2goPrintQueue class 85 self.profile_name = 'UNKNOWN' 86 self.session_name = 'UNKNOWN' 87 88 self.client_instance = client_instance
89 90 @property
91 - def name(self):
92 """\ 93 Return the X2Go print action's name. 94 95 """ 96 return self.__name__
97 98 @property
99 - def description(self):
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
138 - def _humanreadable_filename(self, pdf_file, job_title, target_path):
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
161 162 -class X2goPrintActionPDFVIEW(X2goPrintAction):
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
172 - def __init__(self, client_instance=None, pdfview_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT):
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
239 240 -class X2goPrintActionPDFSAVE(X2goPrintAction):
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
250 - def __init__(self, client_instance=None, save_to_folder=None, logger=None, loglevel=log.loglevel_DEFAULT):
251 """\ 252 @param client_instance: the underlying L{X2goClient} instance 253 @type client_instance: C{obj} 254 @param save_to_folder: saving location for incoming print jobs (PDF files) 255 @type save_to_folder: C{str} 256 @param logger: you can pass an L{X2goLogger} object to the 257 L{X2goPrintActionPDFSAVE} constructor 258 @type logger: C{obj} 259 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 260 constructed with the given loglevel 261 @type loglevel: C{int} 262 263 """ 264 if save_to_folder is None: 265 save_to_folder = defaults.DEFAULT_PDFSAVE_LOCATION 266 if not utils.is_abs_path(save_to_folder): 267 if not save_to_folder.startswith('~'): 268 save_to_folder = os.path.normpath('~/%s' % save_to_folder) 269 save_to_folder = os.path.expanduser(save_to_folder) 270 self.save_to_folder = save_to_folder 271 272 X2goPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) 273 274 self.logger('Save location for incoming PDFs is: %s' % self.save_to_folder, loglevel=log.loglevel_DEBUG) 275 if not os.path.exists(self.save_to_folder): 276 os.makedirs(self.save_to_folder, mode=0755)
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
296 297 -class X2goPrintActionPRINT(X2goPrintAction):
298 """\ 299 Print action that actually prints an incoming print job file. 300 301 """ 302 __name__ = 'PRINT' 303 __decription__= 'UNIX/Win32GDI printing' 304
305 - def __init__(self, client_instance=None, printer=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # give gsprint.exe a little time to find our printer 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 # give the win32api some time to find our printer... 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 # this is nasty!!!! 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
422 423 -class X2goPrintActionPRINTCMD(X2goPrintAction):
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
434 - def __init__(self, client_instance=None, print_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # this is nasty!!!! 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
483 484 -class X2goPrintActionDIALOG(X2goPrintAction):
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
494 - def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
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