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