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

Source Code for Module Gnumed.pycommon.gmMimeLib

  1  # -*- coding: utf-8 -*- 
  2   
  3  """This module encapsulates mime operations. 
  4   
  5          http://www.dwheeler.com/essays/open-files-urls.html 
  6  """ 
  7  #======================================================================================= 
  8  __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>" 
  9  __license__ = "GPL" 
 10   
 11  # stdlib 
 12  import sys 
 13  import os 
 14  import mailcap 
 15  import mimetypes 
 16  import subprocess 
 17  import shutil 
 18  import logging 
 19  import io 
 20   
 21   
 22  # GNUmed 
 23  if __name__ == '__main__': 
 24          sys.path.insert(0, '../../') 
 25  from Gnumed.pycommon import gmShellAPI 
 26  from Gnumed.pycommon import gmTools 
 27  from Gnumed.pycommon import gmCfg2 
 28  from Gnumed.pycommon import gmWorkerThread 
 29   
 30   
 31  _log = logging.getLogger('gm.docs') 
 32   
 33  #======================================================================================= 
34 -def guess_mimetype(filename=None):
35 """Guess mime type of arbitrary file. 36 37 filenames are supposed to be in Unicode 38 """ 39 worst_case = "application/octet-stream" 40 41 _log.debug('guessing mime type of [%s]', filename) 42 43 # 1) use Python libextractor 44 try: 45 import extractor 46 xtract = extractor.Extractor() 47 props = xtract.extract(filename = filename) 48 for prop, val in props: 49 if (prop == 'mimetype') and (val != worst_case): 50 return val 51 except ImportError: 52 _log.debug('module <extractor> (python wrapper for libextractor) not installed') 53 except OSError as exc: 54 # winerror 126, errno 22 55 if exc.errno == 22: 56 _log.exception('module <extractor> (python wrapper for libextractor) not installed') 57 else: 58 raise 59 ret_code = -1 60 61 # 2) use "file" system command 62 # -i get mime type 63 # -b don't display a header 64 mime_guesser_cmd = 'file -i -b "%s"' % filename 65 # this only works on POSIX with 'file' installed (which is standard, however) 66 # it might work on Cygwin installations 67 aPipe = os.popen(mime_guesser_cmd, 'r') 68 if aPipe is None: 69 _log.debug("cannot open pipe to [%s]" % mime_guesser_cmd) 70 else: 71 pipe_output = aPipe.readline().replace('\n', '').strip() 72 ret_code = aPipe.close() 73 if ret_code is None: 74 _log.debug('[%s]: <%s>' % (mime_guesser_cmd, pipe_output)) 75 if pipe_output not in ['', worst_case]: 76 return pipe_output.split(';')[0].strip() 77 else: 78 _log.error('[%s] on %s (%s): failed with exit(%s)' % (mime_guesser_cmd, os.name, sys.platform, ret_code)) 79 80 # 3) use "extract" shell level libextractor wrapper 81 mime_guesser_cmd = 'extract -p mimetype "%s"' % filename 82 aPipe = os.popen(mime_guesser_cmd, 'r') 83 if aPipe is None: 84 _log.debug("cannot open pipe to [%s]" % mime_guesser_cmd) 85 else: 86 pipe_output = aPipe.readline()[11:].replace('\n', '').strip() 87 ret_code = aPipe.close() 88 if ret_code is None: 89 _log.debug('[%s]: <%s>' % (mime_guesser_cmd, pipe_output)) 90 if pipe_output not in ['', worst_case]: 91 return pipe_output 92 else: 93 _log.error('[%s] on %s (%s): failed with exit(%s)' % (mime_guesser_cmd, os.name, sys.platform, ret_code)) 94 95 # If we and up here we either have an insufficient systemwide 96 # magic number file or we suffer from a deficient operating system 97 # alltogether. It can't get much worse if we try ourselves. 98 99 _log.info("OS level mime detection failed, falling back to built-in magic") 100 101 import gmMimeMagic 102 mime_type = gmTools.coalesce(gmMimeMagic.filedesc(filename), worst_case) 103 del gmMimeMagic 104 105 _log.debug('"%s" -> <%s>' % (filename, mime_type)) 106 return mime_type
107 108 #-----------------------------------------------------------------------------------
109 -def get_viewer_cmd(aMimeType = None, aFileName = None, aToken = None):
110 """Return command for viewer for this mime type complete with this file""" 111 112 if aFileName is None: 113 _log.error("You should specify a file name for the replacement of %s.") 114 # last resort: if no file name given replace %s in original with literal '%s' 115 # and hope for the best - we certainly don't want the module default "/dev/null" 116 aFileName = """%s""" 117 118 mailcaps = mailcap.getcaps() 119 (viewer, junk) = mailcap.findmatch(mailcaps, aMimeType, key = 'view', filename = '%s' % aFileName) 120 # FIXME: we should check for "x-token" flags 121 122 _log.debug("<%s> viewer: [%s]" % (aMimeType, viewer)) 123 124 return viewer
125 126 #-----------------------------------------------------------------------------------
127 -def get_editor_cmd(mimetype=None, filename=None):
128 129 if filename is None: 130 _log.error("You should specify a file name for the replacement of %s.") 131 # last resort: if no file name given replace %s in original with literal '%s' 132 # and hope for the best - we certainly don't want the module default "/dev/null" 133 filename = """%s""" 134 135 mailcaps = mailcap.getcaps() 136 (editor, junk) = mailcap.findmatch(mailcaps, mimetype, key = 'edit', filename = '%s' % filename) 137 138 # FIXME: we should check for "x-token" flags 139 140 _log.debug("<%s> editor: [%s]" % (mimetype, editor)) 141 142 return editor
143 144 #-----------------------------------------------------------------------------------
145 -def guess_ext_by_mimetype(mimetype=''):
146 """Return file extension based on what the OS thinks a file of this mimetype should end in.""" 147 148 # ask system first 149 ext = mimetypes.guess_extension(mimetype) 150 if ext is not None: 151 _log.debug('<%s>: *.%s' % (mimetype, ext)) 152 return ext 153 154 _log.error("<%s>: no suitable file extension known to the OS" % mimetype) 155 156 # try to help the OS a bit 157 cfg = gmCfg2.gmCfgData() 158 ext = cfg.get ( 159 group = 'extensions', 160 option = mimetype, 161 source_order = [('user-mime', 'return'), ('system-mime', 'return')] 162 ) 163 164 if ext is not None: 165 _log.debug('<%s>: *.%s (%s)' % (mimetype, ext, candidate)) 166 return ext 167 168 _log.error("<%s>: no suitable file extension found in config files" % mimetype) 169 170 return ext
171 172 #-----------------------------------------------------------------------------------
173 -def guess_ext_for_file(aFile=None):
174 if aFile is None: 175 return None 176 177 (path_name, f_ext) = os.path.splitext(aFile) 178 if f_ext != '': 179 return f_ext 180 181 # try to guess one 182 mime_type = guess_mimetype(aFile) 183 f_ext = guess_ext_by_mimetype(mime_type) 184 if f_ext is None: 185 _log.error('unable to guess file extension for mime type [%s]' % mime_type) 186 return None 187 188 return f_ext
189 190 #-----------------------------------------------------------------------------------
191 -def adjust_extension_by_mimetype(filename):
192 mimetype = guess_mimetype(filename) 193 mime_suffix = guess_ext_by_mimetype(mimetype) 194 if mime_suffix is None: 195 return filename 196 old_name, old_ext = os.path.splitext(filename) 197 if old_ext == '': 198 new_filename = filename + mime_suffix 199 elif old_ext.lower() == mime_suffix.lower(): 200 return filename 201 new_filename = old_name + mime_suffix 202 _log.debug('[%s] -> [%s]', filename, new_filename) 203 try: 204 os.rename(filename, new_filename) 205 return new_filename 206 except OSError: 207 _log.exception('cannot rename, returning original filename') 208 return filename
209 210 #----------------------------------------------------------------------------------- 211 _system_startfile_cmd = None 212 213 open_cmds = { 214 'xdg-open': 'xdg-open "%s"', # nascent standard on Linux 215 'kfmclient': 'kfmclient exec "%s"', # KDE 216 'gnome-open': 'gnome-open "%s"', # GNOME 217 'exo-open': 'exo-open "%s"', 218 'op': 'op "%s"', 219 'open': 'open "%s"', # MacOSX: "open -a AppName file" (-a allows to override the default app for the file type) 220 'cmd.exe': 'cmd.exe /c "%s"' # Windows 221 #'run-mailcap' 222 #'explorer' 223 } 224
225 -def _get_system_startfile_cmd(filename):
226 227 global _system_startfile_cmd 228 229 if _system_startfile_cmd == '': 230 return False, None 231 232 if _system_startfile_cmd is not None: 233 return True, _system_startfile_cmd % filename 234 235 open_cmd_candidates = open_cmds.keys() 236 237 for candidate in open_cmd_candidates: 238 found, binary = gmShellAPI.detect_external_binary(binary = candidate) 239 if not found: 240 continue 241 _system_startfile_cmd = open_cmds[candidate] 242 _log.info('detected local startfile cmd: [%s]', _system_startfile_cmd) 243 return True, _system_startfile_cmd % filename 244 245 _system_startfile_cmd = '' 246 return False, None
247 248 #-----------------------------------------------------------------------------------
249 -def convert_file(filename=None, target_mime=None, target_filename=None, target_extension=None):
250 """Convert file from one format into another. 251 252 target_mime: a mime type 253 """ 254 source_mime = guess_mimetype(filename = filename) 255 if source_mime.lower() == target_mime.lower(): 256 _log.debug('source file [%s] already target mime type [%s]', filename, target_mime) 257 shutil.copyfile(filename, target_filename) 258 return True 259 260 if target_extension is None: 261 tmp, target_extension = os.path.splitext(target_filename) 262 263 base_name = 'gm-convert_file' 264 265 paths = gmTools.gmPaths() 266 local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', base_name) 267 268 candidates = [ base_name, local_script ] #, base_name + u'.bat' 269 found, binary = gmShellAPI.find_first_binary(binaries = candidates) 270 if not found: 271 binary = base_name# + r'.bat' 272 273 cmd_line = [ 274 binary, 275 filename, 276 target_mime, 277 target_extension.strip('.'), 278 target_filename 279 ] 280 _log.debug('converting: %s', cmd_line) 281 try: 282 gm_convert = subprocess.Popen(cmd_line) 283 except OSError: 284 _log.debug('cannot run <%s(.bat)>', base_name) 285 return False 286 gm_convert.communicate() 287 if gm_convert.returncode != 0: 288 _log.error('<%s(.bat)> returned [%s], failed to convert', base_name, gm_convert.returncode) 289 return False 290 291 return True
292 293 #-----------------------------------------------------------------------------------
294 -def __run_file_describer(filename=None):
295 296 base_name = 'gm-describe_file' 297 298 paths = gmTools.gmPaths() 299 local_script = os.path.join(paths.local_base_dir, '..', 'external-tools', base_name) 300 301 candidates = [ base_name, local_script ] #, base_name + u'.bat' 302 found, binary = gmShellAPI.find_first_binary(binaries = candidates) 303 if not found: 304 #binary = base_name# + r'.bat' 305 _log.debug('cannot find <%s(.bat)>', base_name) 306 return (False, _('<%s(.bat)> not found') % base_name) 307 308 desc_fname = gmTools.get_unique_filename() 309 310 cmd_line = [ 311 binary, 312 filename, 313 desc_fname 314 ] 315 _log.debug('describing: %s', cmd_line) 316 try: 317 gm_describe = subprocess.Popen(cmd_line) 318 except OSError: 319 _log.debug('cannot run <%s>', binary) 320 return (False, _('problem with <%s>') % binary) 321 322 gm_describe.communicate() 323 if gm_describe.returncode != 0: 324 _log.error('<%s> returned [%s], failed to convert', binary, gm_describe.returncode) 325 return (False, _('problem with <%s>') % binary) 326 327 try: 328 desc_file = io.open(desc_fname, mode = 'rt', encoding = 'utf8', errors = 'replace') 329 except IOError: 330 _log.exception('cannot open [%s]', desc_fname) 331 return (False, _('problem with <%s>') % binary) 332 333 desc = ''.join(desc_file) 334 desc_file.close() 335 return (True, desc)
336 337 #-----------------------------------------------------------------------------------
338 -def describe_file(filename, callback=None):
339 if callback is None: 340 return __run_file_describer(filename) 341 342 payload_kwargs = {'filename': filename} 343 gmWorkerThread.execute_in_worker_thread ( 344 payload_function = __run_file_describer, 345 payload_kwargs = payload_kwargs, 346 completion_callback = callback 347 )
348 349 #-----------------------------------------------------------------------------------
350 -def call_viewer_on_file(aFile = None, block=None):
351 """Try to find an appropriate viewer with all tricks and call it. 352 353 block: try to detach from viewer or not, None means to use mailcap default 354 """ 355 if not os.path.isdir(aFile): 356 # is the file accessible at all ? 357 try: 358 open(aFile).close() 359 except: 360 _log.exception('cannot read [%s]', aFile) 361 msg = _('[%s] is not a readable file') % aFile 362 return False, msg 363 364 # try to detect any of the UNIX openers 365 found, startfile_cmd = _get_system_startfile_cmd(aFile) 366 if found: 367 if gmShellAPI.run_command_in_shell(command = startfile_cmd, blocking = block): 368 return True, '' 369 370 mime_type = guess_mimetype(aFile) 371 viewer_cmd = get_viewer_cmd(mime_type, aFile) 372 373 if viewer_cmd is not None: 374 if gmShellAPI.run_command_in_shell(command = viewer_cmd, blocking = block): 375 return True, '' 376 377 _log.warning("no viewer found via standard mailcap system") 378 if os.name == "posix": 379 _log.warning("you should add a viewer for this mime type to your mailcap file") 380 381 _log.info("let's see what the OS can do about that") 382 383 # does the file already have an extension ? 384 (path_name, f_ext) = os.path.splitext(aFile) 385 # no 386 if f_ext in ['', '.tmp']: 387 # try to guess one 388 f_ext = guess_ext_by_mimetype(mime_type) 389 if f_ext is None: 390 _log.warning("no suitable file extension found, trying anyway") 391 file_to_display = aFile 392 f_ext = '?unknown?' 393 else: 394 file_to_display = aFile + f_ext 395 shutil.copyfile(aFile, file_to_display) 396 # yes 397 else: 398 file_to_display = aFile 399 400 file_to_display = os.path.normpath(file_to_display) 401 _log.debug("file %s <type %s> (ext %s) -> file %s" % (aFile, mime_type, f_ext, file_to_display)) 402 403 try: 404 os.startfile(file_to_display) 405 return True, '' 406 except AttributeError: 407 _log.exception('os.startfile() does not exist on this platform') 408 except: 409 _log.exception('os.startfile(%s) failed', file_to_display) 410 411 msg = _("Unable to display the file:\n\n" 412 " [%s]\n\n" 413 "Your system does not seem to have a (working)\n" 414 "viewer registered for the file type\n" 415 " [%s]" 416 ) % (file_to_display, mime_type) 417 return False, msg
418 419 #======================================================================================= 420 if __name__ == "__main__": 421 422 if len(sys.argv) < 2: 423 sys.exit() 424 425 if sys.argv[1] != 'test': 426 sys.exit() 427 428 from Gnumed.pycommon import gmI18N 429 430 # for testing: 431 logging.basicConfig(level = logging.DEBUG) 432 433 filename = sys.argv[2] 434 _get_system_startfile_cmd(filename) 435 # print(_system_startfile_cmd) 436 # print(guess_mimetype(filename)) 437 # print(get_viewer_cmd(guess_mimetype(filename), filename)) 438 # print(get_editor_cmd(guess_mimetype(filename), filename)) 439 print(get_editor_cmd('application/x-latex', filename)) 440 print(get_editor_cmd('application/x-tex', filename)) 441 print(get_editor_cmd('text/latex', filename)) 442 print(get_editor_cmd('text/tex', filename)) 443 print(get_editor_cmd('text/plain', filename)) 444 # print(guess_ext_by_mimetype(mimetype=filename)) 445 # call_viewer_on_file(aFile = filename, block=None) 446 # status, desc = describe_file(filename) 447 # print(status) 448 # print(desc) 449