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 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
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
238 (path_name, f_ext) = os.path.splitext(aFile)
239
240 if f_ext in ['', '.tmp']:
241
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
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
270
271
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
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
396
397