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  __version__ = "$Revision: 1.27 $"
      7  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
      8  __license__ = "GPL"
      9
     10  # stdlib
     11  import sys
     12  import os
     13  import mailcap
     14  import mimetypes
     15  import subprocess
     16  import shutil
     17  import logging
     18
     19
     20  # GNUmed
     21  if __name__ == '__main__':
     22          sys.path.insert(0, '../../')
     23  import gmShellAPI, gmTools, gmCfg2
     24
     25
     26  _log = logging.getLogger('gm.docs')
     27  _log.info(__version__)
     28  #=======================================================================================
     29 -def guess_mimetype(aFileName = None):
     30          """Guess mime type of arbitrary file.
     31
     32          filenames are supposed to be in Unicode
     33          """
     34          worst_case = "application/octet-stream"
     35
     36          # 1) use Python libextractor
     37          try:
     38                  import extractor
     39                  xtract = extractor.Extractor()
     40                  props = xtract.extract(filename = aFileName)
     41                  for prop, val in props:
     42                          if (prop == 'mimetype') and (val != worst_case):
     43                                  return val
     44          except ImportError:
     45                  _log.exception('Python wrapper for libextractor not installed.')
     46
     47          ret_code = -1
     48
     49          # 2) use "file" system command
     50          #    -i get mime type
     51          #    -b don't display a header
     52          mime_guesser_cmd = u'file -i -b "%s"' % aFileName
     53          # this only works on POSIX with 'file' installed (which is standard, however)
     54          # it might work on Cygwin installations
     55          aPipe = os.popen(mime_guesser_cmd.encode(sys.getfilesystemencoding()), 'r')
     56          if aPipe is None:
     57                  _log.debug("cannot open pipe to [%s]" % mime_guesser_cmd)
     58          else:
     59                  pipe_output = aPipe.readline().replace('\n', '').strip()
     60                  ret_code = aPipe.close()
     61                  if ret_code is None:
     62                          _log.debug('[%s]: <%s>' % (mime_guesser_cmd, pipe_output))
     63                          if pipe_output not in [u'', worst_case]:
     64                                  return pipe_output
     65                  else:
     66                          _log.error('[%s] on %s (%s): failed with exit(%s)' % (mime_guesser_cmd, os.name, sys.platform, ret_code))
     67
     68          # 3) use "extract" shell level libextractor wrapper
     69          mime_guesser_cmd = 'extract -p mimetype "%s"' % aFileName
     70          aPipe = os.popen(mime_guesser_cmd.encode(sys.getfilesystemencoding()), 'r')
     71          if aPipe is None:
     72                  _log.debug("cannot open pipe to [%s]" % mime_guesser_cmd)
     73          else:
     74                  pipe_output = aPipe.readline()[11:].replace('\n', '').strip()
     75                  ret_code = aPipe.close()
     76                  if ret_code is None:
     77                          _log.debug('[%s]: <%s>' % (mime_guesser_cmd, pipe_output))
     78                          if pipe_output not in [u'', worst_case]:
     79                                  return pipe_output
     80                  else:
     81                          _log.error('[%s] on %s (%s): failed with exit(%s)' % (mime_guesser_cmd, os.name, sys.platform, ret_code))
     82
     83          # If we and up here we either have an insufficient systemwide
     84          # magic number file or we suffer from a deficient operating system
     85          # alltogether. It can't get much worse if we try ourselves.
     86
     87          _log.info("OS level mime detection failed, falling back to built-in magic")
     88
     89          import gmMimeMagic
     90          mime_type = gmTools.coalesce(gmMimeMagic.file(aFileName), worst_case)
     91          del gmMimeMagic
     92
     93          _log.debug('"%s" -> <%s>' % (aFileName, mime_type))
     94          return mime_type
     95  #-----------------------------------------------------------------------------------
     96 -def get_viewer_cmd(aMimeType = None, aFileName = None, aToken = None):
     97          """Return command for viewer for this mime type complete with this file"""
     98
     99          if aFileName is None:
    100                  _log.error("You should specify a file name for the replacement of %s.")
    101                  # last resort: if no file name given replace %s in original with literal '%s'
    102                  # and hope for the best - we certainly don't want the module default "/dev/null"
    103                  aFileName = """%s"""
    104
    105          mailcaps = mailcap.getcaps()
    106          (viewer, junk) = mailcap.findmatch(mailcaps, aMimeType, key = 'view', filename = '%s' % aFileName)
    107          # FIXME: we should check for "x-token" flags
    108
    109          _log.debug("<%s> viewer: [%s]" % (aMimeType, viewer))
    110
    111          return viewer
    112  #-----------------------------------------------------------------------------------
    113 -def get_editor_cmd(mimetype=None, filename=None):
    114
    115          if filename is None:
    116                  _log.error("You should specify a file name for the replacement of %s.")
    117                  # last resort: if no file name given replace %s in original with literal '%s'
    118                  # and hope for the best - we certainly don't want the module default "/dev/null"
    119                  filename = """%s"""
    120
    121          mailcaps = mailcap.getcaps()
    122          (editor, junk) = mailcap.findmatch(mailcaps, mimetype, key = 'edit', filename = '%s' % filename)
    123
    124          # FIXME: we should check for "x-token" flags
    125
    126          _log.debug("<%s> editor: [%s]" % (mimetype, editor))
    127
    128          return editor
    129  #-----------------------------------------------------------------------------------
    130 -def guess_ext_by_mimetype(mimetype=''):
    131          """Return file extension based on what the OS thinks a file of this mimetype should end in."""
    132
    133          # ask system first
    134          ext = mimetypes.guess_extension(mimetype)
    135          if ext is not None:
    136                  _log.debug('<%s>: *.%s' % (mimetype, ext))
    137                  return ext
    138
    139          _log.error("<%s>: no suitable file extension known to the OS" % mimetype)
    140
    141          # try to help the OS a bit
    142          cfg = gmCfg2.gmCfgData()
    143          ext = cfg.get (
    144                  group = u'extensions',
    145                  option = mimetype,
    146                  source_order = [('user-mime', 'return'), ('system-mime', 'return')]
    147          )
    148
    149          if ext is not None:
    150                  _log.debug('<%s>: *.%s (%s)' % (mimetype, ext, candidate))
    151                  return ext
    152
    153          _log.error("<%s>: no suitable file extension found in config files" % mimetype)
    154
    155          return ext
    156  #-----------------------------------------------------------------------------------
    157 -def guess_ext_for_file(aFile=None):
    158          if aFile is None:
    159                  return None
    160
    161          (path_name, f_ext) = os.path.splitext(aFile)
    162          if f_ext != '':
    163                  return f_ext
    164
    165          # try to guess one
    166          mime_type = guess_mimetype(aFile)
    167          f_ext = guess_ext_by_mimetype(mime_type)
    168          if f_ext is None:
    169                  _log.error('unable to guess file extension for mime type [%s]' % mime_type)
    170                  return None
    171
    172          return f_ext
    173  #-----------------------------------------------------------------------------------
    174  _system_startfile_cmd = None
    175
    176  open_cmds = {
    177          'xdg-open': 'xdg-open "%s"',                    # nascent standard on Linux
    178          'kfmclient': 'kfmclient exec "%s"',             # KDE
    179          'gnome-open': 'gnome-open "%s"',                # GNOME
    180          'exo-open': 'exo-open "%s"',
    181          'op': 'op "%s"',
    182          'open': 'open "%s"'                                             # MacOSX: "open -a AppName file" (-a allows to override the default app for the file type)
    183          #'run-mailcap'
    184          #'explorer'
    185  }
    186
    187 -def _get_system_startfile_cmd(filename):
    188
    189          global _system_startfile_cmd
    190
    191          if _system_startfile_cmd == u'':
    192                  return False, None
    193
    194          if _system_startfile_cmd is not None:
    195                  return True, _system_startfile_cmd % filename
    196
    197          open_cmd_candidates = ['xdg-open', 'kfmclient', 'gnome-open', 'exo-open', 'op', 'open']
    198
    199          for candidate in open_cmd_candidates:
    200                  found, binary = gmShellAPI.detect_external_binary(binary = candidate)
    201                  if not found:
    202                          continue
    203                  _system_startfile_cmd = open_cmds[candidate]
    204                  _log.info('detected local startfile cmd: [%s]', _system_startfile_cmd)
    205                  return True, _system_startfile_cmd % filename
    206
    207          _system_startfile_cmd = u''
    208          return False, None
    209  #-----------------------------------------------------------------------------------
    210 -def convert_file(filename=None, target_mime=None, target_filename=None, target_extension=None):
    211          """Convert file from one format into another.
    212
    213                  target_mime: a mime type
    214          """
    215          if target_extension is None:
    216                  tmp, target_extension = os.path.splitext(target_filename)
    217
    218          base_name = u'gm-convert_file'
    219
    220          paths = gmTools.gmPaths()
    221          local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', base_name)
    222
    223          candidates = [ base_name, local_script ]                #, base_name + u'.bat'
    224          found, binary = gmShellAPI.find_first_binary(binaries = candidates)
    225          if not found:
    226                  binary = base_name# + r'.bat'
    227
    228          cmd_line = [
    229                  binary,
    230                  filename,
    231                  target_mime,
    232                  target_extension.strip('.'),
    233                  target_filename
    234          ]
    235          _log.debug('converting: %s', cmd_line)
    236          try:
    237                  gm_convert = subprocess.Popen(cmd_line)
    238          except OSError:
    239                  _log.debug('cannot run <%s(.bat)>', base_name)
    240                  return False
    241          gm_convert.communicate()
    242          if gm_convert.returncode != 0:
    243                  _log.error('<%s(.bat)> returned [%s], failed to convert', base_name, gm_convert.returncode)
    244                  return False
    245
    246          return True
    247  #-----------------------------------------------------------------------------------
    248 -def call_viewer_on_file(aFile = None, block=None):
    249          """Try to find an appropriate viewer with all tricks and call it.
    250
    251          block: try to detach from viewer or not, None means to use mailcap default
    252          """
    253          # does this file exist, actually ?
    254          try:
    255                  open(aFile).close()
    256          except:
    257                  _log.exception('cannot read [%s]', aFile)
    258                  msg = _('[%s] is not a readable file') % aFile
    259                  return False, msg
    260
    261          # try to detect any of the UNIX openers
    262          found, startfile_cmd = _get_system_startfile_cmd(aFile)
    263          if found:
    264                  if gmShellAPI.run_command_in_shell(command = startfile_cmd, blocking = block):
    265                          return True, ''
    266
    267          mime_type = guess_mimetype(aFile)
    268          viewer_cmd = get_viewer_cmd(mime_type, aFile)
    269
    270          if viewer_cmd is not None:
    271                  if gmShellAPI.run_command_in_shell(command=viewer_cmd, blocking=block):
    272                          return True, ''
    273
    274          _log.warning("no viewer found via standard mailcap system")
    275          if os.name == "posix":
    276                  _log.warning("you should add a viewer for this mime type to your mailcap file")
    277          _log.info("let's see what the OS can do about that")
    278
    279          # does the file already have an extension ?
    280          (path_name, f_ext) = os.path.splitext(aFile)
    281          # no
    282          if f_ext in ['', '.tmp']:
    283                  # try to guess one
    284                  f_ext = guess_ext_by_mimetype(mime_type)
    285                  if f_ext is None:
    286                          _log.warning("no suitable file extension found, trying anyway")
    287                          file_to_display = aFile
    288                          f_ext = '?unknown?'
    289                  else:
    290                          file_to_display = aFile + f_ext
    291                          shutil.copyfile(aFile, file_to_display)
    292          # yes
    293          else:
    294                  file_to_display = aFile
    295
    296          file_to_display = os.path.normpath(file_to_display)
    297          _log.debug("file %s <type %s> (ext %s) -> file %s" % (aFile, mime_type, f_ext, file_to_display))
    298
    299          try:
    300                  os.startfile(file_to_display)
    301          except:
    302                  _log.exception('os.startfile(%s) failed', file_to_display)
    303                  msg = _("Unable to display the file:\n\n"
    304                                  " [%s]\n\n"
    305                                  "Your system does not seem to have a (working)\n"
    306                                  "viewer registered for the file type\n"
    307                                  " [%s]"
    308                  ) % (file_to_display, mime_type)
    309                  return False, msg
    310
    311          # don't kill the file from under the (possibly async) viewer
    312  #       if file_to_display != aFile:
    313  #               os.remove(file_to_display)
    314
    315          return True, ''
    316  #=======================================================================================
    317  if __name__ == "__main__":
    318
    319          if len(sys.argv) > 1 and sys.argv[1] == u'test':
    320
    321                  filename = sys.argv[2]
    322
    323                  _get_system_startfile_cmd(filename)
    324                  print _system_startfile_cmd
    325                  #print guess_mimetype(filename)
    326                  #print get_viewer_cmd(guess_mimetype(filename), filename)
    327                  #print guess_ext_by_mimetype(mimetype=filename)
    328