Package Gnumed :: Package pycommon :: Module gmMimeLib |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmMimeLib

      1  # -*- coding: latin-1 -*-
      2
      3  """This module encapsulates mime operations.
      4  """
      5  #=======================================================================================
      6  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/pycommon/gmMimeLib.py,v $
      7  # $Id: gmMimeLib.py,v 1.27 2010-01-03 18:15:17 ncq Exp $
      8  __version__ = "$Revision: 1.27 $"
      9  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
     10  __license__ = "GPL"
     11
     12  # stdlib
     13  import os, mailcap, sys, mimetypes, shutil, logging
     14
     15
     16  # GNUmed
     17  if __name__ == '__main__':
     18          sys.path.insert(0, '../../')
     19  import gmShellAPI, gmTools, gmCfg2
     20
     21
     22  _log = logging.getLogger('gm.docs')
     23  _log.info(__version__)
     24  #=======================================================================================
     25 -def guess_mimetype(aFileName = None):
     26          """Guess mime type of arbitrary file.
     27
     28          filenames are supposed to be in Unicode
     29          """
     30          worst_case = "application/octet-stream"
     31
     32          # 1) use Python libextractor
     33          try:
     34                  import extractor
     35                  xtract = extractor.Extractor()
     36                  props = xtract.extract(filename = aFileName)
     37                  for prop, val in props:
     38                          if (prop == 'mimetype') and (val != worst_case):
     39                                  return val
     40          except ImportError:
     41                  _log.exception('Python wrapper for libextractor not installed.')
     42
     43          ret_code = -1
     44
     45          # 2) use "file" system command
     46          #    -i get mime type
     47          #    -b don't display a header
     48          mime_guesser_cmd = u'file -i -b "%s"' % aFileName
     49          # this only works on POSIX with 'file' installed (which is standard, however)
     50          # it might work on Cygwin installations
     51          aPipe = os.popen(mime_guesser_cmd.encode(sys.getfilesystemencoding()), 'r')
     52          if aPipe is None:
     53                  _log.debug("cannot open pipe to [%s]" % mime_guesser_cmd)
     54          else:
     55                  pipe_output = aPipe.readline().replace('\n', '').strip()
     56                  ret_code = aPipe.close()
     57                  if ret_code is None:
     58                          _log.debug('[%s]: <%s>' % (mime_guesser_cmd, pipe_output))
     59                          if pipe_output not in [u'', worst_case]:
     60                                  return pipe_output
     61                  else:
     62                          _log.error('[%s] on %s (%s): failed with exit(%s)' % (mime_guesser_cmd, os.name, sys.platform, ret_code))
     63
     64          # 3) use "extract" shell level libextractor wrapper
     65          mime_guesser_cmd = 'extract -p mimetype "%s"' % aFileName
     66          aPipe = os.popen(mime_guesser_cmd.encode(sys.getfilesystemencoding()), 'r')
     67          if aPipe is None:
     68                  _log.debug("cannot open pipe to [%s]" % mime_guesser_cmd)
     69          else:
     70                  pipe_output = aPipe.readline()[11:].replace('\n', '').strip()
     71                  ret_code = aPipe.close()
     72                  if ret_code is None:
     73                          _log.debug('[%s]: <%s>' % (mime_guesser_cmd, pipe_output))
     74                          if pipe_output not in [u'', worst_case]:
     75                                  return pipe_output
     76                  else:
     77                          _log.error('[%s] on %s (%s): failed with exit(%s)' % (mime_guesser_cmd, os.name, sys.platform, ret_code))
     78
     79          # If we and up here we either have an insufficient systemwide
     80          # magic number file or we suffer from a deficient operating system
     81          # alltogether. It can't get much worse if we try ourselves.
     82
     83          _log.info("OS level mime detection failed, falling back to built-in magic")
     84
     85          import gmMimeMagic
     86          mime_type = gmTools.coalesce(gmMimeMagic.file(aFileName), worst_case)
     87          del gmMimeMagic
     88
     89          _log.debug('"%s" -> <%s>' % (aFileName, mime_type))
     90          return mime_type
     91  #-----------------------------------------------------------------------------------
     92 -def get_viewer_cmd(aMimeType = None, aFileName = None, aToken = None):
     93          """Return command for viewer for this mime type complete with this file"""
     94
     95          if aFileName is None:
     96                  _log.error("You should specify a file name for the replacement of %s.")
     97                  # last resort: if no file name given replace %s in original with literal '%s'
     98                  # and hope for the best - we certainly don't want the module default "/dev/null"
     99                  aFileName = """%s"""
    100
    101          mailcaps = mailcap.getcaps()
    102          (viewer, junk) = mailcap.findmatch(mailcaps, aMimeType, key = 'view', filename = '%s' % aFileName)
    103          # FIXME: we should check for "x-token" flags
    104
    105          _log.debug("<%s> viewer: [%s]" % (aMimeType, viewer))
    106
    107          return viewer
    108  #-----------------------------------------------------------------------------------
    109 -def get_editor_cmd(mimetype=None, filename=None):
    110
    111          if filename is None:
    112                  _log.error("You should specify a file name for the replacement of %s.")
    113                  # last resort: if no file name given replace %s in original with literal '%s'
    114                  # and hope for the best - we certainly don't want the module default "/dev/null"
    115                  filename = """%s"""
    116
    117          mailcaps = mailcap.getcaps()
    118          (editor, junk) = mailcap.findmatch(mailcaps, mimetype, key = 'edit', filename = '%s' % filename)
    119
    120          # FIXME: we should check for "x-token" flags
    121
    122          _log.debug("<%s> editor: [%s]" % (mimetype, editor))
    123
    124          return editor
    125  #-----------------------------------------------------------------------------------
    126 -def guess_ext_by_mimetype(mimetype=''):
    127          """Return file extension based on what the OS thinks a file of this mimetype should end in."""
    128
    129          # ask system first
    130          ext = mimetypes.guess_extension(mimetype)
    131          if ext is not None:
    132                  _log.debug('<%s>: *.%s' % (mimetype, ext))
    133                  return ext
    134
    135          _log.error("<%s>: no suitable file extension known to the OS" % mimetype)
    136
    137          # try to help the OS a bit
    138          cfg = gmCfg2.gmCfgData()
    139          ext = cfg.get (
    140                  group = u'extensions',
    141                  option = mimetype,
    142                  source_order = [('user-mime', 'return'), ('system-mime', 'return')]
    143          )
    144
    145          if ext is not None:
    146                  _log.debug('<%s>: *.%s (%s)' % (mimetype, ext, candidate))
    147                  return ext
    148
    149          _log.error("<%s>: no suitable file extension found in config files" % mimetype)
    150
    151          return ext
    152  #-----------------------------------------------------------------------------------
    153 -def guess_ext_for_file(aFile=None):
    154          if aFile is None:
    155                  return None
    156
    157          (path_name, f_ext) = os.path.splitext(aFile)
    158          if f_ext != '':
    159                  return f_ext
    160
    161          # try to guess one
    162          mime_type = guess_mimetype(aFile)
    163          f_ext = guess_ext_by_mimetype(mime_type)
    164          if f_ext is None:
    165                  _log.error('unable to guess file extension for mime type [%s]' % mime_type)
    166                  return None
    167
    168          return f_ext
    169  #-----------------------------------------------------------------------------------
    170  _system_startfile_cmd = None
    171
    172  open_cmds = {
    173          'xdg-open': 'xdg-open "%s"',                    # nascent standard on Linux
    174          'kfmclient': 'kfmclient exec "%s"',             # KDE
    175          'gnome-open': 'gnome-open "%s"',                # GNOME
    176          'exo-open': 'exo-open "%s"',
    177          'op': 'op "%s"',
    178          'open': 'open "%s"'                                             # MacOSX: "open -a AppName file" (-a allows to override the default app for the file type)
    179          #'run-mailcap'
    180          #'explorer'
    181  }
    182
    183 -def _get_system_startfile_cmd(filename):
    184
    185          global _system_startfile_cmd
    186
    187          if _system_startfile_cmd == u'':
    188                  return False, None
    189
    190          if _system_startfile_cmd is not None:
    191                  return True, _system_startfile_cmd % filename
    192
    193          open_cmd_candidates = ['xdg-open', 'kfmclient', 'gnome-open', 'exo-open', 'op', 'open']
    194
    195          for candidate in open_cmd_candidates:
    196                  found, binary = gmShellAPI.detect_external_binary(binary = candidate)
    197                  if not found:
    198                          continue
    199                  _system_startfile_cmd = open_cmds[candidate]
    200                  _log.info('detected local startfile cmd: [%s]', _system_startfile_cmd)
    201                  return True, _system_startfile_cmd % filename
    202
    203          _system_startfile_cmd = u''
    204          return False, None
    205  #-----------------------------------------------------------------------------------
    206 -def call_viewer_on_file(aFile = None, block=None):
    207          """Try to find an appropriate viewer with all tricks and call it.
    208
    209          block: try to detach from viewer or not, None means to use mailcap default
    210          """
    211          # does this file exist, actually ?
    212          try:
    213                  open(aFile).close()
    214          except:
    215                  _log.exception('cannot read [%s]', aFile)
    216                  msg = _('[%s] is not a readable file') % aFile
    217                  return False, msg
    218
    219          # try to detect any of the UNIX openers
    220          found, startfile_cmd = _get_system_startfile_cmd(aFile)
    221          if found:
    222                  if gmShellAPI.run_command_in_shell(command = startfile_cmd, blocking = block):
    223                          return True, ''
    224
    225          mime_type = guess_mimetype(aFile)
    226          viewer_cmd = get_viewer_cmd(mime_type, aFile)
    227
    228          if viewer_cmd is not None:
    229                  if gmShellAPI.run_command_in_shell(command=viewer_cmd, blocking=block):
    230                          return True, ''
    231
    232          _log.warning("no viewer found via standard mailcap system")
    233          if os.name == "posix":
    234                  _log.warning("you should add a viewer for this mime type to your mailcap file")
    235          _log.info("let's see what the OS can do about that")
    236
    237          # does the file already have an extension ?
    238          (path_name, f_ext) = os.path.splitext(aFile)
    239          # no
    240          if f_ext in ['', '.tmp']:
    241                  # try to guess one
    242                  f_ext = guess_ext_by_mimetype(mime_type)
    243                  if f_ext is None:
    244                          _log.warning("no suitable file extension found, trying anyway")
    245                          file_to_display = aFile
    246                          f_ext = '?unknown?'
    247                  else:
    248                          file_to_display = aFile + f_ext
    249                          shutil.copyfile(aFile, file_to_display)
    250          # yes
    251          else:
    252                  file_to_display = aFile
    253
    254          file_to_display = os.path.normpath(file_to_display)
    255          _log.debug("file %s <type %s> (ext %s) -> file %s" % (aFile, mime_type, f_ext, file_to_display))
    256
    257          try:
    258                  os.startfile(file_to_display)
    259          except:
    260                  _log.exception('os.startfile(%s) failed', file_to_display)
    261                  msg = _("Unable to display the file:\n\n"
    262                                  " [%s]\n\n"
    263                                  "Your system does not seem to have a (working)\n"
    264                                  "viewer registered for the file type\n"
    265                                  " [%s]"
    266                  ) % (file_to_display, mime_type)
    267                  return False, msg
    268
    269          # don't kill the file from under the (possibly async) viewer
    270  #       if file_to_display != aFile:
    271  #               os.remove(file_to_display)
    272
    273          return True, ''
    274  #=======================================================================================
    275  if __name__ == "__main__":
    276
    277          if len(sys.argv) > 1 and sys.argv[1] == u'test':
    278
    279                  filename = sys.argv[2]
    280
    281                  _get_system_startfile_cmd(filename)
    282                  print _system_startfile_cmd
    283                  #print guess_mimetype(filename)
    284                  #print get_viewer_cmd(guess_mimetype(filename), filename)
    285                  #print guess_ext_by_mimetype(mimetype=filename)
    286
    287  #=======================================================================================
    288  # $Log: gmMimeLib.py,v $
    289  # Revision 1.27  2010-01-03 18:15:17  ncq
    290  # - get-editor-cmd
    291  #
    292  # Revision 1.26  2009/11/24 20:48:15  ncq
    293  # - quote open command arg
    294  #
    295  # Revision 1.25  2009/09/17 21:52:40  ncq
    296  # - properly log exceptions
    297  #
    298  # Revision 1.24  2009/09/01 22:24:09  ncq
    299  # - document MacOSX open behaviour
    300  #
    301  # Revision 1.23  2008/12/01 12:12:37  ncq
    302  # - turn file-open candidates into list so we can influence detection order
    303  #
    304  # Revision 1.22  2008/07/22 13:54:25  ncq
    305  # - xdg-open is intended to become the standard so look for that first
    306  # - some cleanup
    307  #
    308  # Revision 1.21  2008/03/11 16:58:11  ncq
    309  # - much improved detection of startfile cmd under UNIX
    310  #
    311  # Revision 1.20  2008/01/14 20:28:21  ncq
    312  # - use detect_external_binary()
    313  #
    314  # Revision 1.19  2008/01/11 16:11:40  ncq
    315  # - support gnome-open just like kfmclient
    316  #
    317  # Revision 1.18  2008/01/05 16:38:56  ncq
    318  # - eventually use python libextractor module if available
    319  # - do not assume every POSIX system knows mailcap, MacOSX doesn't
    320  #
    321  # Revision 1.17  2007/12/23 11:58:50  ncq
    322  # - use gmCfg2
    323  #
    324  # Revision 1.16  2007/12/12 16:17:15  ncq
    325  # - better logger names
    326  #
    327  # Revision 1.15  2007/12/11 14:31:12  ncq
    328  # - use std logging
    329  #
    330  # Revision 1.14  2007/10/12 14:19:18  ncq
    331  # - if file ext is ".tmp" and we were unable to run a viewer on that
    332  #   file - try to replace the extension based on the mime type
    333  #
    334  # Revision 1.13  2007/08/31 23:04:04  ncq
    335  # - on KDE support kfmclient
    336  #
    337  # Revision 1.12  2007/08/08 21:23:20  ncq
    338  # - improve wording
    339  #
    340  # Revision 1.11  2007/08/07 21:40:36  ncq
    341  # - streamline code
    342  # - teach guess_ext_by_mimetype() about mime_type2file_name.conf
    343  #
    344  # Revision 1.10  2007/07/09 12:39:36  ncq
    345  # - cleanup, improved logging
    346  #
    347  # Revision 1.9  2007/03/31 21:20:14  ncq
    348  # - os.popen() needs encoded command strings
    349  # - fix test suite
    350  #
    351  # Revision 1.8  2006/12/23 15:24:28  ncq
    352  # - use gmShellAPI
    353  #
    354  # Revision 1.7  2006/10/31 17:19:26  ncq
    355  # - some ERRORs are really WARNings
    356  #
    357  # Revision 1.6  2006/09/12 17:23:30  ncq
    358  # - add block argument to call_viewer_on_file()
    359  # - improve file access checks and raise exception on failure
    360  # - improve some error messages
    361  #
    362  # Revision 1.5  2006/06/17 13:15:10  shilbert
    363  # - shutil import was added to make it work on Windows
    364  #
    365  # Revision 1.4  2006/05/16 15:50:51  ncq
    366  # - properly escape filename
    367  #
    368  # Revision 1.3  2006/05/01 18:47:16  ncq
    369  # - add use of "extract" command in mimetype guessing
    370  #
    371  # Revision 1.2  2004/10/11 19:08:08  ncq
    372  # - guess_ext_for_file()
    373  #
    374  # Revision 1.1  2004/02/25 09:30:13  ncq
    375  # - moved here from python-common
    376  #
    377  # Revision 1.5  2003/11/17 10:56:36  sjtan
    378  #
    379  # synced and commiting.
    380  #
    381  # Revision 1.1  2003/10/23 06:02:39  sjtan
    382  #
    383  # manual edit areas modelled after r.terry's specs.
    384  #
    385  # Revision 1.4  2003/06/26 21:34:43  ncq
    386  # - fatal->verbose
    387  #
    388  # Revision 1.3  2003/04/20 15:33:03  ncq
    389  # - call_viewer_on_file() belongs here, I guess
    390  #
    391  # Revision 1.2  2003/02/17 16:17:20  ncq
    392  # - fix typo
    393  #
    394  # Revision 1.1  2003/02/14 00:22:17  ncq
    395  # - mime ops for general use
    396  #
    397