| Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: latin-1 -*-
2
3 """This module encapsulates mime operations.
4 """
5 #=======================================================================================
6 # $Source: /cvsroot/gnumed/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 #=======================================================================================
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 #-----------------------------------------------------------------------------------
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 #-----------------------------------------------------------------------------------
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 #-----------------------------------------------------------------------------------
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 #-----------------------------------------------------------------------------------
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
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 #-----------------------------------------------------------------------------------
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 if not (os.path.isfile(aFile) and os.access(aFile, os.R_OK)):
213 msg = '[%s] is not a readable file'
214 _log.error(msg, aFile)
215 raise IOError(msg % aFile)
216
217 # try to detect any of the UNIX openers
218 found, startfile_cmd = _get_system_startfile_cmd(aFile)
219 if found:
220 if gmShellAPI.run_command_in_shell(command = startfile_cmd, blocking = block):
221 return True, ''
222
223 mime_type = guess_mimetype(aFile)
224 viewer_cmd = get_viewer_cmd(mime_type, aFile)
225
226 if viewer_cmd is not None:
227 if gmShellAPI.run_command_in_shell(command=viewer_cmd, blocking=block):
228 return True, ''
229
230 _log.warning("no viewer found via standard mailcap system")
231 if os.name == "posix":
232 _log.warning("you should add a viewer for this mime type to your mailcap file")
233 _log.info("let's see what the OS can do about that")
234
235 # does the file already have an extension ?
236 (path_name, f_ext) = os.path.splitext(aFile)
237 # no
238 if f_ext in ['', '.tmp']:
239 # try to guess one
240 f_ext = guess_ext_by_mimetype(mime_type)
241 if f_ext is None:
242 _log.warning("no suitable file extension found, trying anyway")
243 file_to_display = aFile
244 f_ext = '?unknown?'
245 else:
246 file_to_display = aFile + f_ext
247 shutil.copyfile(aFile, file_to_display)
248 # yes
249 else:
250 file_to_display = aFile
251
252 file_to_display = os.path.normpath(file_to_display)
253 _log.debug("file %s <type %s> (ext %s) -> file %s" % (aFile, mime_type, f_ext, file_to_display))
254
255 try:
256 os.startfile(file_to_display)
257 except:
258 _log.exception('os.startfile(%s) failed', file_to_display)
259 msg = _("Unable to display the file:\n\n"
260 " [%s]\n\n"
261 "Your system does not seem to have a (working)\n"
262 "viewer registered for the file type\n"
263 " [%s]"
264 ) % (file_to_display, mime_type)
265 return False, msg
266
267 # don't kill the file from under the (possibly async) viewer
268 # if file_to_display != aFile:
269 # os.remove(file_to_display)
270
271 return True, ''
272 #=======================================================================================
273 if __name__ == "__main__":
274
275 if len(sys.argv) > 1 and sys.argv[1] == u'test':
276
277 filename = sys.argv[2]
278
279 _get_system_startfile_cmd(filename)
280 print _system_startfile_cmd
281 #print guess_mimetype(filename)
282 #print get_viewer_cmd(guess_mimetype(filename), filename)
283 #print guess_ext_by_mimetype(mimetype=filename)
284
285 #=======================================================================================
286 # $Log: gmMimeLib.py,v $
287 # Revision 1.27 2010/01/03 18:15:17 ncq
288 # - get-editor-cmd
289 #
290 # Revision 1.26 2009/11/24 20:48:15 ncq
291 # - quote open command arg
292 #
293 # Revision 1.25 2009/09/17 21:52:40 ncq
294 # - properly log exceptions
295 #
296 # Revision 1.24 2009/09/01 22:24:09 ncq
297 # - document MacOSX open behaviour
298 #
299 # Revision 1.23 2008/12/01 12:12:37 ncq
300 # - turn file-open candidates into list so we can influence detection order
301 #
302 # Revision 1.22 2008/07/22 13:54:25 ncq
303 # - xdg-open is intended to become the standard so look for that first
304 # - some cleanup
305 #
306 # Revision 1.21 2008/03/11 16:58:11 ncq
307 # - much improved detection of startfile cmd under UNIX
308 #
309 # Revision 1.20 2008/01/14 20:28:21 ncq
310 # - use detect_external_binary()
311 #
312 # Revision 1.19 2008/01/11 16:11:40 ncq
313 # - support gnome-open just like kfmclient
314 #
315 # Revision 1.18 2008/01/05 16:38:56 ncq
316 # - eventually use python libextractor module if available
317 # - do not assume every POSIX system knows mailcap, MacOSX doesn't
318 #
319 # Revision 1.17 2007/12/23 11:58:50 ncq
320 # - use gmCfg2
321 #
322 # Revision 1.16 2007/12/12 16:17:15 ncq
323 # - better logger names
324 #
325 # Revision 1.15 2007/12/11 14:31:12 ncq
326 # - use std logging
327 #
328 # Revision 1.14 2007/10/12 14:19:18 ncq
329 # - if file ext is ".tmp" and we were unable to run a viewer on that
330 # file - try to replace the extension based on the mime type
331 #
332 # Revision 1.13 2007/08/31 23:04:04 ncq
333 # - on KDE support kfmclient
334 #
335 # Revision 1.12 2007/08/08 21:23:20 ncq
336 # - improve wording
337 #
338 # Revision 1.11 2007/08/07 21:40:36 ncq
339 # - streamline code
340 # - teach guess_ext_by_mimetype() about mime_type2file_name.conf
341 #
342 # Revision 1.10 2007/07/09 12:39:36 ncq
343 # - cleanup, improved logging
344 #
345 # Revision 1.9 2007/03/31 21:20:14 ncq
346 # - os.popen() needs encoded command strings
347 # - fix test suite
348 #
349 # Revision 1.8 2006/12/23 15:24:28 ncq
350 # - use gmShellAPI
351 #
352 # Revision 1.7 2006/10/31 17:19:26 ncq
353 # - some ERRORs are really WARNings
354 #
355 # Revision 1.6 2006/09/12 17:23:30 ncq
356 # - add block argument to call_viewer_on_file()
357 # - improve file access checks and raise exception on failure
358 # - improve some error messages
359 #
360 # Revision 1.5 2006/06/17 13:15:10 shilbert
361 # - shutil import was added to make it work on Windows
362 #
363 # Revision 1.4 2006/05/16 15:50:51 ncq
364 # - properly escape filename
365 #
366 # Revision 1.3 2006/05/01 18:47:16 ncq
367 # - add use of "extract" command in mimetype guessing
368 #
369 # Revision 1.2 2004/10/11 19:08:08 ncq
370 # - guess_ext_for_file()
371 #
372 # Revision 1.1 2004/02/25 09:30:13 ncq
373 # - moved here from python-common
374 #
375 # Revision 1.5 2003/11/17 10:56:36 sjtan
376 #
377 # synced and commiting.
378 #
379 # Revision 1.1 2003/10/23 06:02:39 sjtan
380 #
381 # manual edit areas modelled after r.terry's specs.
382 #
383 # Revision 1.4 2003/06/26 21:34:43 ncq
384 # - fatal->verbose
385 #
386 # Revision 1.3 2003/04/20 15:33:03 ncq
387 # - call_viewer_on_file() belongs here, I guess
388 #
389 # Revision 1.2 2003/02/17 16:17:20 ncq
390 # - fix typo
391 #
392 # Revision 1.1 2003/02/14 00:22:17 ncq
393 # - mime ops for general use
394 #
395
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:26 2010 | http://epydoc.sourceforge.net |