1
2
3 """This module encapsulates mime operations.
4 """
5
6
7
8 __version__ = "$Revision: 1.27 $"
9 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
10 __license__ = "GPL"
11
12
13 import os, mailcap, sys, mimetypes, shutil, logging
14
15
16
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
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
46
47
48 mime_guesser_cmd = u'file -i -b "%s"' % aFileName
49
50
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
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
80
81
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
98
99 aFileName = """%s"""
100
101 mailcaps = mailcap.getcaps()
102 (viewer, junk) = mailcap.findmatch(mailcaps, aMimeType, key = 'view', filename = '%s' % aFileName)
103
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
114
115 filename = """%s"""
116
117 mailcaps = mailcap.getcaps()
118 (editor, junk) = mailcap.findmatch(mailcaps, mimetype, key = 'edit', filename = '%s' % filename)
119
120
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
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
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
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"',
174 'kfmclient': 'kfmclient exec "%s"',
175 'gnome-open': 'gnome-open "%s"',
176 'exo-open': 'exo-open "%s"',
177 'op': 'op "%s"',
178 'open': 'open "%s"'
179
180
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
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
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
236 (path_name, f_ext) = os.path.splitext(aFile)
237
238 if f_ext in ['', '.tmp']:
239
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
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
268
269
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395