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

Source Code for Module x2go.printactions

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2016 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2Go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU Affero General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2Go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU Affero General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Affero General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 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  # modules 
 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  # Python X2Go modules 
 48  import log 
 49  import defaults 
 50  # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) 
 51  import utils 
 52  import x2go_exceptions 
 53   
 54  _PRINT_ENV = os.environ.copy() 
55 56 57 -class X2GoPrintAction(object):
58 59 __name__ = 'NAME' 60 __description__ = 'DESCRIPTION' 61
62 - def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # these get set from within the X2GoPrintQueue class 84 self.profile_name = 'UNKNOWN' 85 self.session_name = 'UNKNOWN' 86 87 self.client_instance = client_instance
88 89 @property
90 - def name(self):
91 """\ 92 Return the X2Go print action's name. 93 94 """ 95 return self.__name__
96 97 @property
98 - def description(self):
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
137 - def _humanreadable_filename(self, pdf_file, job_title, target_path):
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
160 161 -class X2GoPrintActionPDFVIEW(X2GoPrintAction):
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
171 - def __init__(self, client_instance=None, pdfview_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT):
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
238 239 -class X2GoPrintActionPDFSAVE(X2GoPrintAction):
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
249 - def __init__(self, client_instance=None, save_to_folder=None, logger=None, loglevel=log.loglevel_DEFAULT):
250 """\ 251 @param client_instance: the underlying L{X2GoClient} instance 252 @type client_instance: C{obj} 253 @param save_to_folder: saving location for incoming print jobs (PDF files) 254 @type save_to_folder: C{str} 255 @param logger: you can pass an L{X2GoLogger} object to the 256 L{X2GoPrintActionPDFSAVE} constructor 257 @type logger: C{obj} 258 @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be 259 constructed with the given loglevel 260 @type loglevel: C{int} 261 262 """ 263 if save_to_folder is None: 264 save_to_folder = defaults.DEFAULT_PDFSAVE_LOCATION 265 if not utils.is_abs_path(save_to_folder): 266 if not save_to_folder.startswith('~'): 267 save_to_folder = os.path.normpath('~/%s' % save_to_folder) 268 save_to_folder = os.path.expanduser(save_to_folder) 269 self.save_to_folder = save_to_folder 270 271 X2GoPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) 272 273 self.logger('Save location for incoming PDFs is: %s' % self.save_to_folder, loglevel=log.loglevel_DEBUG) 274 if not os.path.exists(self.save_to_folder): 275 os.makedirs(self.save_to_folder, mode=0755)
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
295 296 -class X2GoPrintActionPRINT(X2GoPrintAction):
297 """\ 298 Print action that actually prints an incoming print job file. 299 300 """ 301 __name__ = 'PRINT' 302 __decription__= 'UNIX/Win32GDI printing' 303
304 - def __init__(self, client_instance=None, printer=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # give gsprint.exe a little time to find our printer 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 # give the win32api some time to find our printer... 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 # this is nasty!!!! 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
421 422 -class X2GoPrintActionPRINTCMD(X2GoPrintAction):
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
433 - def __init__(self, client_instance=None, print_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT):
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 # this is nasty!!!! 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
482 483 -class X2GoPrintActionDIALOG(X2GoPrintAction):
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
493 - def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT):
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