Remove commented out debug statements, fix lookup_type()
[rox-lib.git] / python / rox / mime.py
blobc688d80ed656e46e26cda3556549d5ebfeb641d9
1 # Access to shared MIME database
2 # $Id$
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.html for
15 information about the format of these files."""
17 import os
18 import stat
19 import fnmatch
21 import rox
22 from rox.i18n import _expand_lang
24 from xml.dom import Node, minidom
26 exts={}
27 globs={}
28 literals={}
29 types={}
31 try:
32 _home=os.environ['HOME']
33 except:
34 _home=None
35 try:
36 _xdg_data_home=os.environ['XDG_DATA_HOME']
37 except:
38 if _home:
39 _xdg_data_home=os.path.join(_home, '.local', 'share')
40 else:
41 _xdg_data_home=None
42 try:
43 _xdg_data_dirs=os.environ['XDG_DATA_DIRS']
44 except:
45 _xdg_data_dirs='/usr/local/share:/usr/share'
47 mimedirs=[]
48 if _xdg_data_home:
49 _user_install=os.path.join(_xdg_data_home, 'mime')
50 if os.access(_user_install, os.R_OK):
51 mimedirs.append(_user_install)
52 else:
53 # See if we have the old directory
54 _old_user_install=os.path.join(_home, '.mime')
55 if os.access(_old_user_install, os.R_OK):
56 mimedirs.append(_old_user_install)
57 rox.info(_("WARNING: %s not found for shared MIME database version %s, using %s for version %s") % (_user_install, '0.11',
58 _old_user_install, '0.10'))
59 else:
60 # Neither old nor new. Assume new for installing files
61 mimedirs.append(_user_install)
64 for _dir in _xdg_data_dirs.split(':'):
65 mimedirs.append(os.path.join(_dir, 'mime'))
67 def _get_node_data(node):
68 """Get text of XML node"""
69 return ''.join([n.nodeValue for n in node.childNodes]).strip()
71 class MIMEtype:
72 """Type holding data about a MIME type"""
73 def __init__(self, media, subtype=None):
74 """Create the object. Call as either MIMEtype('media', 'subtype')
75 or MIMEtype('media/subtype')"""
76 if subtype is None and media.find('/')>0:
77 media, subtype=media.split('/', 1)
78 self.media=media
79 self.subtype=subtype
80 self.comment=None
81 try:
82 self.lang=_expand_lang(os.environ['LANG'])
83 except:
84 self.lang=None
85 self.saved_lang=None
87 def __load(self):
88 """Loads comment for current language. Use get_comment() instead."""
89 for dir in mimedirs:
90 path=os.path.join(dir, self.media, self.subtype+'.xml')
91 try:
92 doc=minidom.parse(path)
93 if doc is None:
94 continue
95 for section in doc.documentElement.childNodes:
96 if section.nodeType != Node.ELEMENT_NODE:
97 continue
98 if section.localName=='comment':
99 nlang=section.getAttribute('xml:lang')
100 if type(self.lang)== type(str) and nlang!=self.lang:
101 continue
102 if type(self.lang)== type(list) and nlang not in self.lang:
103 continue
104 self.comment=_get_node_data(section)
105 self.saved_lang=self.lang
106 return
107 except IOError:
108 pass
110 def get_comment(self):
111 """Returns comment for current language, loading it if needed."""
112 if self.comment is None or self.lang!=self.saved_lang:
113 try:
114 self.__load()
115 except:
116 pass
117 return self.comment
119 def get_name(self):
120 """Return name of type, as media/subtype"""
121 return self.media+'/'+self.subtype
122 def __str__(self):
123 """Convert to string"""
124 return self.media+'/'+self.subtype
125 def __repr__(self):
126 if self.comment:
127 return '['+self.media+'/'+self.subtype+': '+self.comment+']'
128 return '['+self.media+'/'+self.subtype+']'
130 # Some well-known types
131 types['text/plain']=text=MIMEtype('text', 'plain')
132 types['inode/blockdevice']=inode_block=MIMEtype('inode', 'blockdevice')
133 types['inode/chardevice']=inode_char=MIMEtype('inode', 'chardevice')
134 types['inode/directory']=inode_dir=MIMEtype('inode', 'directory')
135 types['inode/fifo']=inode_fifo=MIMEtype('inode', 'fifo')
136 types['inode/socket']=inode_socket=MIMEtype('inode', 'socket')
137 types['inode/symlink']=inode_symlink=MIMEtype('inode', 'symlink')
138 types['inode/door']=inode_door=MIMEtype('inode', 'door')
139 types['application/executable']=app_exe=MIMEtype('application', 'executable')
141 def import_glob_file(dir):
142 """Loads name matching information from a MIME directory."""
143 path=os.path.join(dir, 'globs')
144 try:
145 lines=file(path, 'r').readlines()
146 except:
147 return
149 for line in lines:
150 if line[0]=='#':
151 continue
152 line=line.strip()
153 type, pattern=line.split(':', 1)
155 try:
156 mtype=types[type]
157 except:
158 mtype=MIMEtype(type)
159 types[type]=mtype
161 globs[pattern]=mtype
162 if pattern[:2]=='*.':
163 if pattern[2:].find('*')<0 and pattern[2:].find('[')<0 and pattern[2:].find('?')<0:
164 exts[pattern[2:]]=mtype
165 if pattern.find('*')<0 and pattern.find('[')<0 and pattern.find('?')<0:
166 literals[pattern]=mtype
168 for dir in mimedirs:
169 import_glob_file(dir)
171 def get_type_by_name(path):
172 """Returns type of file by its name, or None if not known"""
173 try:
174 leaf=os.path.basename(path)
175 lleaf=leaf.lower()
176 if literals.has_key(leaf):
177 return literals[leaf]
178 if literals.has_key(lleaf):
179 return literals[lleaf]
180 ext=leaf
181 while ext.find('.')>=0:
182 p=ext.find('.')
183 ext=ext[p+1:]
184 if exts.has_key(ext):
185 return exts[ext]
186 ext=lleaf
187 while ext.find('.')>=0:
188 p=ext.find('.')
189 ext=ext[p+1:]
190 if exts.has_key(ext):
191 return exts[ext]
192 for glob in globs:
193 if fnmatch.fnmatch(leaf, glob):
194 return globs[glob]
195 if fnmatch.fnmatch(lleaf, glob):
196 return globs[glob]
198 except:
199 pass
200 return None
202 def get_type(path, follow=1, name_pri=100):
203 """Returns type of file indicated by path.
204 path - pathname to check (need not exist)
205 follow - when reading file, follow symbolic links
206 name_pri - Priority to do name matches. 100=override magic"""
207 # name_pri is not implemented
208 try:
209 if follow:
210 st=os.stat(path)
211 else:
212 st=os.lstat(path)
213 except:
214 t=get_type_by_name(path)
215 if t is None:
216 return text
217 return t
218 if stat.S_ISREG(st.st_mode):
219 t=get_type_by_name(path)
220 if t is None:
221 if stat.S_IMODE(st.st_mode) & 0111:
222 return app_exe
223 else:
224 return text
225 return t
226 elif stat.S_ISDIR(st.st_mode):
227 return inode_dir
228 elif stat.S_ISCHR(st.st_mode):
229 return inode_char
230 elif stat.S_ISBLK(st.st_mode):
231 return inode_block
232 elif stat.S_ISFIFO(st.st_mode):
233 return inode_fifo
234 elif stat.S_ISLNK(st.st_mode):
235 return inode_symlink
236 elif stat.S_ISSOCK(st.st_mode):
237 return inode_sock
238 return inode_door
240 def install_mime_info(application, package_file = None):
241 """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
242 If package_file is None, install <app_dir>/<application>.xml.
243 If already installed, does nothing. May overwrite an existing
244 file with the same name (if the contents are different)"""
245 application += '.xml'
246 if not package_file:
247 package_file = os.path.join(rox.app_dir, application)
249 new_data = file(package_file).read()
251 # See if the file is already installed
253 for x in mimedirs:
254 test = os.path.join(x, 'packages', application)
255 try:
256 old_data = file(test).read()
257 except:
258 continue
259 if old_data == new_data:
260 return # Already installed
262 # Not already installed; add a new copy
263 try:
264 # Create the directory structure...
266 packages = os.path.join(mimedirs[0], 'packages')
268 if not os.path.exists(mimedirs[0]): os.mkdir(mimedirs[0])
269 if not os.path.exists(packages): os.mkdir(packages)
271 # Write the file...
272 new_file = os.path.join(packages, application)
273 file(new_file, 'w').write(new_data)
275 # Update the database...
276 if os.spawnlp(os.P_WAIT, 'update-mime-database', 'update-mime-database', mimedirs[0]):
277 os.unlink(new_file)
278 raise Exception(_("The 'update-mime-database' command returned an error code!\n" \
279 "Make sure you have the freedesktop.org shared MIME package:\n" \
280 "http://www.freedesktop.org/standards/shared-mime-info.html"))
281 except:
282 rox.report_exception()
284 def lookup_type(media, subtype=None, allow_new=1):
285 """Return MIMEtype for given type, or None if not defined. Call as
286 either lookup_type('media', 'subtype') or lookup_type('media/subtype')"""
287 if subtype is None:
288 type=media
289 else:
290 type=media+'/'+subtype
292 if types.has_key(type):
293 return types[type]
294 if allow_new:
295 return MIMEtype(media, subtype)
296 return None
298 def test(name):
299 """Print results for name. Test routine"""
300 t=get_type(name)
301 print name, t, t.get_comment()
303 if __name__=='__main__':
304 import sys
305 if len(sys.argv)<2:
306 test('file.txt')
307 else:
308 for f in sys.argv[1:]:
309 test(f)