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