1 """This module provides access to the shared MIME database.
3 types is a dictionary of all known MIME types, indexed by the type name, e.g.
4 types['application/x-python']
6 Applications can install information about MIME types by storing an
7 XML file as <MIME>/packages/<application>.xml and running the
8 update-mime-database command, which is provided by the freedesktop.org
9 shared mime database package.
11 See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
12 information about the format of these files."""
19 from rox
import i18n
, _
21 from xml
.dom
import Node
, minidom
, XML_NAMESPACE
23 FREE_NS
= 'http://www.freedesktop.org/standards/shared-mime-info'
25 exts
= {} # Maps extensions to types
26 globs
= [] # List of (glob, type) pairs
27 literals
= {} # Maps liternal names to types
29 types
= {} # Maps MIME names to type objects
31 _home
= os
.environ
.get('HOME', '/')
32 _xdg_data_home
= os
.environ
.get('XDG_DATA_HOME',
33 os
.path
.join(_home
, '.local', 'share'))
35 _xdg_data_dirs
= os
.environ
.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share')
39 _user_install
= os
.path
.join(_xdg_data_home
, 'mime')
40 if os
.access(_user_install
, os
.R_OK
):
41 mimedirs
.append(_user_install
)
43 # See if we have the old directory
44 _old_user_install
= os
.path
.join(_home
, '.mime')
45 if os
.access(_old_user_install
, os
.R_OK
):
46 mimedirs
.append(_old_user_install
)
47 rox
.info(_("WARNING: %s not found for shared MIME database version %s, "
48 "using %s for version %s") %
49 (_user_install
, '0.11', _old_user_install
, '0.10'))
51 # Neither old nor new. Assume new for installing files
52 mimedirs
.append(_user_install
)
54 for _dir
in _xdg_data_dirs
.split(':'):
55 mimedirs
.append(os
.path
.join(_dir
, 'mime'))
57 def _get_node_data(node
):
58 """Get text of XML node"""
59 return ''.join([n
.nodeValue
for n
in node
.childNodes
]).strip()
61 def lookup(media
, subtype
= None):
62 "Get the MIMEtype object for this type, creating a new one if needed."
63 if subtype
is None and '/' in media
:
64 media
, subtype
= media
.split('/', 1)
65 if (media
, subtype
) not in types
:
66 types
[(media
, subtype
)] = MIMEtype(media
, subtype
)
67 return types
[(media
, subtype
)]
70 """Type holding data about a MIME type"""
71 def __init__(self
, media
, subtype
):
72 "Don't use this constructor directly; use mime.lookup() instead."
73 assert media
and '/' not in media
74 assert subtype
and '/' not in subtype
75 assert (media
, subtype
) not in types
78 self
.subtype
= subtype
82 "Loads comment for current language. Use get_comment() instead."
84 path
= os
.path
.join(dir, self
.media
, self
.subtype
+ '.xml')
85 if not os
.path
.exists(path
):
88 doc
= minidom
.parse(path
)
91 for comment
in doc
.documentElement
.getElementsByTagNameNS(FREE_NS
, 'comment'):
92 lang
= comment
.getAttributeNS(XML_NAMESPACE
, 'lang') or 'en'
93 goodness
= 1 + (lang
in i18n
.langs
)
94 if goodness
> self
.comment
[0]:
95 self
.comment
= (goodness
, _get_node_data(comment
))
96 if goodness
== 2: return
98 def get_comment(self
):
99 """Returns comment for current language, loading it if needed."""
100 # Should we ever reload?
101 if self
.comment
is None:
102 self
.comment
= (0, str(self
))
104 return self
.comment
[1]
107 return self
.media
+ '/' + self
.subtype
110 return '[%s: %s]' % (self
, self
.comment
or '(comment not loaded)')
112 # Some well-known types
113 text
= lookup('text', 'plain')
114 inode_block
= lookup('inode', 'blockdevice')
115 inode_char
= lookup('inode', 'chardevice')
116 inode_dir
= lookup('inode', 'directory')
117 inode_fifo
= lookup('inode', 'fifo')
118 inode_socket
= lookup('inode', 'socket')
119 inode_symlink
= lookup('inode', 'symlink')
120 inode_door
= lookup('inode', 'door')
121 app_exe
= lookup('application', 'executable')
123 def _import_glob_file(dir):
124 """Loads name matching information from a MIME directory."""
125 path
= os
.path
.join(dir, 'globs')
126 if not os
.path
.exists(path
):
129 for line
in file(path
):
130 if line
.startswith('#'): continue
133 type, pattern
= line
.split(':', 1)
136 if pattern
.startswith('*.'):
138 if not ('*' in rest
or '[' in rest
or '?' in rest
):
141 if '*' in pattern
or '[' in pattern
or '?' in pattern
:
142 globs
.append((pattern
, mtype
))
144 literals
[pattern
] = mtype
147 _import_glob_file(dir)
149 # Sort globs by length
150 globs
.sort(lambda a
, b
: cmp(len(b
[0]), len(a
[0])))
152 def get_type_by_name(path
):
153 """Returns type of file by its name, or None if not known"""
154 leaf
= os
.path
.basename(path
)
156 return literals
[leaf
]
159 if lleaf
in literals
:
160 return literals
[lleaf
]
176 for (glob
, type) in globs
:
177 if fnmatch
.fnmatch(leaf
, glob
):
179 if fnmatch
.fnmatch(lleaf
, glob
):
183 def get_type(path
, follow
=1, name_pri
=100):
184 """Returns type of file indicated by path.
185 path - pathname to check (need not exist)
186 follow - when reading file, follow symbolic links
187 name_pri - Priority to do name matches. 100=override magic"""
188 # name_pri is not implemented
195 t
= get_type_by_name(path
)
198 if stat
.S_ISREG(st
.st_mode
):
199 t
= get_type_by_name(path
)
201 if stat
.S_IMODE(st
.st_mode
) & 0111:
206 elif stat
.S_ISDIR(st
.st_mode
): return inode_dir
207 elif stat
.S_ISCHR(st
.st_mode
): return inode_char
208 elif stat
.S_ISBLK(st
.st_mode
): return inode_block
209 elif stat
.S_ISFIFO(st
.st_mode
): return inode_fifo
210 elif stat
.S_ISLNK(st
.st_mode
): return inode_symlink
211 elif stat
.S_ISSOCK(st
.st_mode
): return inode_socket
214 def install_mime_info(application
, package_file
= None):
215 """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
216 If package_file is None, install <app_dir>/<application>.xml.
217 If already installed, does nothing. May overwrite an existing
218 file with the same name (if the contents are different)"""
219 application
+= '.xml'
221 package_file
= os
.path
.join(rox
.app_dir
, application
)
223 new_data
= file(package_file
).read()
225 # See if the file is already installed
228 test
= os
.path
.join(x
, 'packages', application
)
230 old_data
= file(test
).read()
233 if old_data
== new_data
:
234 return # Already installed
236 # Not already installed; add a new copy
238 # Create the directory structure...
240 packages
= os
.path
.join(mimedirs
[0], 'packages')
241 if not os
.path
.exists(packages
): os
.makedirs(packages
)
244 new_file
= os
.path
.join(packages
, application
)
245 file(new_file
, 'w').write(new_data
)
247 # Update the database...
248 if os
.spawnlp(os
.P_WAIT
, 'update-mime-database', 'update-mime-database', mimedirs
[0]):
250 raise Exception(_("The 'update-mime-database' command returned an error code!\n" \
251 "Make sure you have the freedesktop.org shared MIME package:\n" \
252 "http://www.freedesktop.org/standards/shared-mime-info.html"))
254 rox
.report_exception()
257 """Print results for name. Test routine"""
259 print name
, t
, t
.get_comment()
261 if __name__
=='__main__':
266 for f
in sys
.argv
[1:]: