Package x2go :: Module printactions

[frames] | no frames]

# Source Code for Module x2go.printactions

      1  # -*- coding: utf-8 -*-
      2
      3  # Copyright (C) 2010-2015 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