1 # Access to shared MIME database
4 """This module provides access to the shared MIME database.
6 types is a dictionary of all known MIME types, indexed by the type name, e.g.
7 types['application/x-python']
9 Applications can install information about MIME types by storing an
10 XML file as <MIME>/packages/<application>.xml and running the
11 update-mime-database command, which is provided by the freedesktop.org
12 shared mime database package.
14 See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
15 information about the format of these files."""
24 from xml
.dom
import Node
, minidom
, XML_NAMESPACE
26 FREE_NS
= 'http://www.freedesktop.org/standards/shared-mime-info'
28 exts
= {} # Maps extensions to types
29 globs
= [] # List of (glob, type) pairs
30 literals
= {} # Maps liternal names to types
32 types
= {} # Maps MIME names to type objects
34 _home
= os
.environ
.get('HOME', '/')
35 _xdg_data_home
= os
.environ
.get('XDG_DATA_HOME',
36 os
.path
.join(_home
, '.local', 'share'))
38 _xdg_data_dirs
= os
.environ
.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share')
42 _user_install
= os
.path
.join(_xdg_data_home
, 'mime')
43 if os
.access(_user_install
, os
.R_OK
):
44 mimedirs
.append(_user_install
)
46 # See if we have the old directory
47 _old_user_install
= os
.path
.join(_home
, '.mime')
48 if os
.access(_old_user_install
, os
.R_OK
):
49 mimedirs
.append(_old_user_install
)
50 rox
.info(_("WARNING: %s not found for shared MIME database version %s, "
51 "using %s for version %s") %
52 (_user_install
, '0.11', _old_user_install
, '0.10'))
54 # Neither old nor new. Assume new for installing files
55 mimedirs
.append(_user_install
)
57 for _dir
in _xdg_data_dirs
.split(':'):
58 mimedirs
.append(os
.path
.join(_dir
, 'mime'))
60 def _get_node_data(node
):
61 """Get text of XML node"""
62 return ''.join([n
.nodeValue
for n
in node
.childNodes
]).strip()
64 def lookup(media
, subtype
= None):
65 "Get the MIMEtype object for this type, creating a new one if needed."
66 if subtype
is None and '/' in media
:
67 media
, subtype
= media
.split('/', 1)
68 if (media
, subtype
) not in types
:
69 types
[(media
, subtype
)] = MIMEtype(media
, subtype
)
70 return types
[(media
, subtype
)]
73 """Type holding data about a MIME type"""
74 def __init__(self
, media
, subtype
):
75 "Don't use this constructor directly; use mime.lookup() instead."
76 assert media
and '/' not in media
77 assert subtype
and '/' not in subtype
78 assert (media
, subtype
) not in types
81 self
.subtype
= subtype
85 "Loads comment for current language. Use get_comment() instead."
87 path
= os
.path
.join(dir, self
.media
, self
.subtype
+ '.xml')
88 if not os
.path
.exists(path
):
91 doc
= minidom
.parse(path
)
94 for comment
in doc
.documentElement
.getElementsByTagNameNS(FREE_NS
, 'comment'):
95 lang
= comment
.getAttributeNS(XML_NAMESPACE
, 'lang') or 'en'
96 goodness
= 1 + (lang
in i18n
.langs
)
97 if goodness
> self
.comment
[0]:
98 self
.comment
= (goodness
, _get_node_data(comment
))
99 if goodness
== 2: return
101 def get_comment(self
):
102 """Returns comment for current language, loading it if needed."""
103 # Should we ever reload?
104 if self
.comment
is None:
105 self
.comment
= (0, str(self
))
107 return self
.comment
[1]
110 return self
.media
+ '/' + self
.subtype
113 return '[%s: %s]' % (self
, self
.comment
or '(comment not loaded)')
115 # Some well-known types
116 text
= lookup('text', 'plain')
117 inode_block
= lookup('inode', 'blockdevice')
118 inode_char
= lookup('inode', 'chardevice')
119 inode_dir
= lookup('inode', 'directory')
120 inode_fifo
= lookup('inode', 'fifo')
121 inode_socket
= lookup('inode', 'socket')
122 inode_symlink
= lookup('inode', 'symlink')
123 inode_door
= lookup('inode', 'door')
124 app_exe
= lookup('application', 'executable')
126 def _import_glob_file(dir):
127 """Loads name matching information from a MIME directory."""
128 path
= os
.path
.join(dir, 'globs')
129 if not os
.path
.exists(path
):
132 for line
in file(path
):
133 if line
.startswith('#'): continue
136 type, pattern
= line
.split(':', 1)
139 if pattern
.startswith('*.'):
141 if not ('*' in rest
or '[' in rest
or '?' in rest
):
144 if '*' in pattern
or '[' in pattern
or '?' in pattern
:
145 globs
.append((pattern
, mtype
))
147 literals
[pattern
] = mtype
150 _import_glob_file(dir)
152 def longest_first(a
, b
):
155 if len(stra
)>len(strb
):
157 elif len(stra
)<len(strb
):
161 # Sort globs by length
162 globs
.sort(longest_first
)
164 def get_type_by_name(path
):
165 """Returns type of file by its name, or None if not known"""
166 leaf
= os
.path
.basename(path
)
168 return literals
[leaf
]
171 if lleaf
in literals
:
172 return literals
[lleaf
]
188 for (glob
, type) in globs
:
189 if fnmatch
.fnmatch(leaf
, glob
):
191 if fnmatch
.fnmatch(lleaf
, glob
):
195 def get_type(path
, follow
=1, name_pri
=100):
196 """Returns type of file indicated by path.
197 path - pathname to check (need not exist)
198 follow - when reading file, follow symbolic links
199 name_pri - Priority to do name matches. 100=override magic"""
200 # name_pri is not implemented
207 t
= get_type_by_name(path
)
210 if stat
.S_ISREG(st
.st_mode
):
211 t
= get_type_by_name(path
)
213 if stat
.S_IMODE(st
.st_mode
) & 0111:
218 elif stat
.S_ISDIR(st
.st_mode
): return inode_dir
219 elif stat
.S_ISCHR(st
.st_mode
): return inode_char
220 elif stat
.S_ISBLK(st
.st_mode
): return inode_block
221 elif stat
.S_ISFIFO(st
.st_mode
): return inode_fifo
222 elif stat
.S_ISLNK(st
.st_mode
): return inode_symlink
223 elif stat
.S_ISSOCK(st
.st_mode
): return inode_socket
226 def install_mime_info(application
, package_file
= None):
227 """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
228 If package_file is None, install <app_dir>/<application>.xml.
229 If already installed, does nothing. May overwrite an existing
230 file with the same name (if the contents are different)"""
231 application
+= '.xml'
233 package_file
= os
.path
.join(rox
.app_dir
, application
)
235 new_data
= file(package_file
).read()
237 # See if the file is already installed
240 test
= os
.path
.join(x
, 'packages', application
)
242 old_data
= file(test
).read()
245 if old_data
== new_data
:
246 return # Already installed
248 # Not already installed; add a new copy
250 # Create the directory structure...
252 packages
= os
.path
.join(mimedirs
[0], 'packages')
253 if not os
.path
.exists(packages
): os
.makedirs(packages
)
256 new_file
= os
.path
.join(packages
, application
)
257 file(new_file
, 'w').write(new_data
)
259 # Update the database...
260 if os
.spawnlp(os
.P_WAIT
, 'update-mime-database', 'update-mime-database', mimedirs
[0]):
262 raise Exception(_("The 'update-mime-database' command returned an error code!\n" \
263 "Make sure you have the freedesktop.org shared MIME package:\n" \
264 "http://www.freedesktop.org/standards/shared-mime-info.html"))
266 rox
.report_exception()
269 """Print results for name. Test routine"""
271 print name
, t
, t
.get_comment()
273 if __name__
=='__main__':
278 for f
in sys
.argv
[1:]: