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."""
20 from rox
import i18n
, _
, basedir
22 from xml
.dom
import Node
, minidom
, XML_NAMESPACE
24 FREE_NS
= 'http://www.freedesktop.org/standards/shared-mime-info'
26 types
= {} # Maps MIME names to type objects
28 # Icon sizes when requesting MIME type icon
32 ICON_SIZE_UNSCALED
=None
34 exts
= None # Maps extensions to types
35 globs
= None # List of (glob, type) pairs
36 literals
= None # Maps liternal names to types
38 def _get_node_data(node
):
39 """Get text of XML node"""
40 return ''.join([n
.nodeValue
for n
in node
.childNodes
]).strip()
42 def lookup(media
, subtype
= None):
43 "Get the MIMEtype object for this type, creating a new one if needed."
44 if subtype
is None and '/' in media
:
45 media
, subtype
= media
.split('/', 1)
46 if (media
, subtype
) not in types
:
47 types
[(media
, subtype
)] = MIMEtype(media
, subtype
)
48 return types
[(media
, subtype
)]
51 """Type holding data about a MIME type"""
52 def __init__(self
, media
, subtype
):
53 "Don't use this constructor directly; use mime.lookup() instead."
54 assert media
and '/' not in media
55 assert subtype
and '/' not in subtype
56 assert (media
, subtype
) not in types
59 self
.subtype
= subtype
63 "Loads comment for current language. Use get_comment() instead."
64 resource
= os
.path
.join('mime', self
.media
, self
.subtype
+ '.xml')
65 for path
in basedir
.load_data_paths(resource
):
66 doc
= minidom
.parse(path
)
69 for comment
in doc
.documentElement
.getElementsByTagNameNS(FREE_NS
, 'comment'):
70 lang
= comment
.getAttributeNS(XML_NAMESPACE
, 'lang') or 'en'
71 goodness
= 1 + (lang
in i18n
.langs
)
72 if goodness
> self
._comment
[0]:
73 self
._comment
= (goodness
, _get_node_data(comment
))
74 if goodness
== 2: return
76 def get_comment(self
):
77 """Returns comment for current language, loading it if needed."""
78 # Should we ever reload?
79 if self
._comment
is None:
80 self
._comment
= (0, str(self
))
82 return self
._comment
[1]
85 return self
.media
+ '/' + self
.subtype
88 return '[%s: %s]' % (self
, self
._comment
or '(comment not loaded)')
90 def get_icon(self
, size
=None):
91 """Return a GdkPixbuf with the icon for this type. If size
92 is None then the image is returned at its natural size,
93 otherwise the image is scaled to that width with the height
94 at the correct aspect ratio. The constants
95 ICON_SIZE_{HUGE,LARGE,SMALL} match the sizes used by the
97 # I suppose it would make more sense to move the code
98 # from saving to here...
100 base
=saving
.image_for_type(self
.media
+ '/' + self
.subtype
)
101 if not base
or not size
:
104 h
=int(base
.get_width()*float(size
)/base
.get_height())
105 return base
.scale_simple(size
, h
, rox
.g
.gdk
.INTERP_BILINEAR
)
107 # Some well-known types
108 text
= lookup('text', 'plain')
109 inode_block
= lookup('inode', 'blockdevice')
110 inode_char
= lookup('inode', 'chardevice')
111 inode_dir
= lookup('inode', 'directory')
112 inode_fifo
= lookup('inode', 'fifo')
113 inode_socket
= lookup('inode', 'socket')
114 inode_symlink
= lookup('inode', 'symlink')
115 inode_door
= lookup('inode', 'door')
116 app_exe
= lookup('application', 'executable')
118 _cache_uptodate
= False
120 def _cache_database():
121 global exts
, globs
, literals
, _cache_uptodate
123 _cache_uptodate
= True
125 exts
= {} # Maps extensions to types
126 globs
= [] # List of (glob, type) pairs
127 literals
= {} # Maps liternal names to types
129 def _import_glob_file(path
):
130 """Loads name matching information from a MIME directory."""
131 for line
in file(path
):
132 if line
.startswith('#'): continue
135 type_name
, pattern
= line
.split(':', 1)
136 mtype
= lookup(type_name
)
138 if pattern
.startswith('*.'):
140 if not ('*' in rest
or '[' in rest
or '?' in rest
):
143 if '*' in pattern
or '[' in pattern
or '?' in pattern
:
144 globs
.append((pattern
, mtype
))
146 literals
[pattern
] = mtype
148 for path
in basedir
.load_data_paths(os
.path
.join('mime', 'globs')):
149 _import_glob_file(path
)
151 # Sort globs by length
152 globs
.sort(lambda a
, b
: cmp(len(b
[0]), len(a
[0])))
154 def get_type_by_name(path
):
155 """Returns type of file by its name, or None if not known"""
156 if not _cache_uptodate
:
159 leaf
= os
.path
.basename(path
)
161 return literals
[leaf
]
164 if lleaf
in literals
:
165 return literals
[lleaf
]
181 for (glob
, mime_type
) in globs
:
182 if fnmatch
.fnmatch(leaf
, glob
):
184 if fnmatch
.fnmatch(lleaf
, glob
):
188 def get_type(path
, follow
=1, name_pri
=100):
189 """Returns type of file indicated by path.
190 path - pathname to check (need not exist)
191 follow - when reading file, follow symbolic links
192 name_pri - Priority to do name matches. 100=override magic"""
193 if not _cache_uptodate
:
195 # name_pri is not implemented
202 t
= get_type_by_name(path
)
205 if stat
.S_ISREG(st
.st_mode
):
206 t
= get_type_by_name(path
)
208 if stat
.S_IMODE(st
.st_mode
) & 0111:
213 elif stat
.S_ISDIR(st
.st_mode
): return inode_dir
214 elif stat
.S_ISCHR(st
.st_mode
): return inode_char
215 elif stat
.S_ISBLK(st
.st_mode
): return inode_block
216 elif stat
.S_ISFIFO(st
.st_mode
): return inode_fifo
217 elif stat
.S_ISLNK(st
.st_mode
): return inode_symlink
218 elif stat
.S_ISSOCK(st
.st_mode
): return inode_socket
221 def install_mime_info(application
, package_file
= None):
222 """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
223 If package_file is None, install <app_dir>/<application>.xml.
224 If already installed, does nothing. May overwrite an existing
225 file with the same name (if the contents are different)"""
226 application
+= '.xml'
228 package_file
= os
.path
.join(rox
.app_dir
, application
)
230 new_data
= file(package_file
).read()
232 # See if the file is already installed
234 package_dir
= os
.path
.join('mime', 'packages')
235 resource
= os
.path
.join(package_dir
, application
)
236 for x
in basedir
.load_data_paths(resource
):
238 old_data
= file(x
).read()
241 if old_data
== new_data
:
242 return # Already installed
244 global _cache_uptodate
245 _cache_uptodate
= False
247 # Not already installed; add a new copy
249 # Create the directory structure...
250 new_file
= os
.path
.join(basedir
.save_data_path(package_dir
), application
)
253 file(new_file
, 'w').write(new_data
)
255 # Update the database...
256 if os
.path
.isdir('/uri/0install/zero-install.sourceforge.net'):
257 command
= '/uri/0install/zero-install.sourceforge.net/bin/update-mime-database'
259 command
= 'update-mime-database'
260 if os
.spawnlp(os
.P_WAIT
, command
, command
, basedir
.save_data_path('mime')):
262 raise Exception(_("The '%s' 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") % command
)
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:]: