4 from xml
.parsers
import expat
5 from xml
.dom
import XML_NAMESPACE
, Node
10 home_mime
= os
.path
.join(os
.environ
['HOME'], '.mime')
11 user_override
= os
.path
.join(home_mime
, 'packages', 'Override.xml')
13 FREE_NS
='http://www.freedesktop.org/standards/shared-mime-info'
16 return ''.join([text
.nodeValue
for text
in node
.childNodes
17 if text
.nodeType
== Node
.TEXT_NODE
])
19 class Invalid(Exception):
24 types
[name
] = MIME_Type(name
)
28 def __init__(self
, name
):
29 assert name
not in types
30 self
.media
, self
.subtype
= name
.split('/')
38 def add_comment(self
, lang
, comment
, user
):
39 self
.comments
.append((lang
, comment
, user
))
41 def add_xml(self
, uri
, name
, user
):
42 self
.xml
.append((uri
, name
, user
))
44 def add_magic(self
, prio
, root
, user
):
45 self
.magic
.append((prio
, root
, user
))
47 def add_other(self
, element
, user
):
48 self
.others
.append((element
, user
))
50 def add_glob(self
, pattern
, user
):
51 self
.globs
.append((pattern
, user
))
53 def get_comment(self
):
55 for lang
, comment
, user
in self
.comments
:
59 return best
or self
.get_name()
62 return self
.media
+ '/' + self
.subtype
64 def make(self
, klass
, list):
65 return [klass(self
, item
) for item
in list]
67 def get_comments(self
): return self
.make(Comment
, self
.comments
)
68 def get_globs(self
): return self
.make(Glob
, self
.globs
)
69 def get_magic(self
): return self
.make(Magic
, self
.magic
)
70 def get_xml(self
): return self
.make(XML
, self
.xml
)
71 def get_others(self
): return self
.make(Other
, self
.others
)
73 def remove_user(self
):
74 for list in ['comments', 'globs', 'magic', 'xml', 'others']:
76 [x
for x
in getattr(self
, list) if not x
[-1]])
79 "MIME_Type.get_* functions return a list of these."
80 def __init__(self
, type, item
= None):
83 self
.item
= self
.get_blank_item()
93 assert self
.user
in (True, False)
95 def get_blank_item(self
):
96 raise Exception("Can't create fields of this type yet")
99 return "<%s>" % self
.item
101 def add_subtree(self
, model
, iter, grey
):
104 def __cmp__(self
, b
):
105 # Note: returns 1 (different) if same and both users set
106 return cmp(str(self
), str(b
)) or self
.user
or b
.user
112 "Returns True on success."
113 if not hasattr(self
, 'add_edit_widgets'):
114 rox
.alert('Sorry, MIME-Editor does not support editing fields of this type')
118 box
.set_has_separator(False)
119 vbox
= g
.VBox(False, 4)
120 vbox
.set_border_width(4)
121 box
.vbox
.pack_start(vbox
, True, True, 0)
122 self
.add_edit_widgets(vbox
)
123 box
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
124 box
.add_button(g
.STOCK_OK
, g
.RESPONSE_OK
)
125 box
.set_default_response(g
.RESPONSE_OK
)
130 if resp
== g
.RESPONSE_OK
:
132 self
.commit_edit(box
)
139 rox
.report_exception()
143 def commit_edit(self
, box
):
144 raise Invalid('TODO: Not implemented')
146 def delete_from_node(self
, type_node
):
147 raise Invalid("TODO: Can't delete/edit fields of this type yet")
149 def add_to_node(self
, node
):
150 raise Invalid("TODO: Can't add/edit fields of this type yet")
154 doc
, node
= get_override_type(self
.type.get_name())
155 self
.delete_from_node(node
)
158 def commit_edit(self
, box
):
159 doc
, node
= get_override_type(self
.type.get_name())
161 self
.delete_from_node(node
)
162 self
.add_to_node(node
)
166 class Comment(Field
):
167 def get_blank_item(self
):
168 return (None, 'Unknown format')
171 lang
, data
= self
.item
173 lang
= '[' + lang
+ '] '
178 def add_edit_widgets(self
, vbox
):
180 g
.Label("Enter a brief description of the type, eg 'HTML Page'.\n"
181 "Leave the language blank unless this is a translation, in \n"
182 "which case enter the country code (eg 'fr')."),
184 hbox
= g
.HBox(False, 0)
185 vbox
.pack_start(hbox
, False, True, 0)
187 self
.lang
= g
.Entry()
188 self
.lang
.set_text(self
.item
[0] or '')
189 hbox
.pack_start(g
.Label('Language'), False, True, 0)
190 hbox
.pack_start(self
.lang
, True, True, 0)
191 self
.lang
.set_activates_default(True)
193 hbox
= g
.HBox(False, 0)
194 vbox
.pack_start(hbox
, False, True, 0)
196 self
.entry
= g
.Entry()
197 self
.entry
.set_text(self
.item
[1])
198 hbox
.pack_start(g
.Label('Description'), False, True, 0)
199 hbox
.pack_start(self
.entry
, True, True, 0)
200 self
.entry
.grab_focus()
201 self
.entry
.set_activates_default(True)
203 def delete_from_node(self
, node
):
204 lang
= self
.item
[0] or ''
205 for x
in node
.childNodes
:
206 if x
.nodeType
!= Node
.ELEMENT_NODE
: continue
207 if x
.localName
== 'comment' and x
.namespaceURI
== FREE_NS
:
208 if (x
.getAttributeNS(XML_NAMESPACE
, 'lang') or '') == lang
and \
209 data(x
) == self
.item
[1]:
210 x
.parentNode
.removeChild(x
)
213 raise Exception("Can't find this comment in Override.xml!")
215 def add_to_node(self
, node
):
216 new_lang
= self
.lang
.get_text()
217 new
= self
.entry
.get_text()
219 raise Invalid("Comment can't be empty")
220 comment
= node
.ownerDocument
.createElementNS(FREE_NS
, 'comment')
222 comment
.setAttributeNS(XML_NAMESPACE
, 'xml:lang', new_lang
)
223 data
= node
.ownerDocument
.createTextNode(new
)
224 comment
.appendChild(data
)
225 node
.appendChild(comment
)
228 def get_blank_item(self
):
232 return "Match '%s'" % self
.item
234 def add_edit_widgets(self
, vbox
):
236 g
.Label("Enter a glob pattern which matches files of this type.\n"
237 "Special characters are:\n"
238 "? - any one character\n"
239 "* - zero or more characters\n"
240 "[abc] - any character between the brackets\n"
241 "Example: '*.html' matches all files ending in '.html'"),
243 self
.entry
= g
.Entry()
244 self
.entry
.set_text(self
.item
)
245 vbox
.pack_start(self
.entry
, False, True, 0)
246 self
.entry
.set_activates_default(True)
248 def delete_from_node(self
, node
):
249 for x
in node
.childNodes
:
250 if x
.nodeType
!= Node
.ELEMENT_NODE
: continue
251 if x
.localName
== 'glob' and x
.namespaceURI
== FREE_NS
:
252 if x
.getAttributeNS(None, 'pattern') == self
.item
:
253 x
.parentNode
.removeChild(x
)
256 raise Exception("Can't find this pattern in Override.xml!")
258 def add_to_node(self
, node
):
259 new
= self
.entry
.get_text()
261 raise Invalid("Pattern can't be empty")
262 glob
= node
.ownerDocument
.createElementNS(FREE_NS
, 'glob')
263 glob
.setAttributeNS(None, 'pattern', new
)
264 node
.appendChild(glob
)
267 def get_blank_item(self
):
268 return (None, Match(None, True))
271 prio
, match
= self
.item
272 return "Match with priority %s" % prio
274 def add_subtree(self
, model
, parent
, grey
):
275 def build(match
, parent
):
276 for m
in match
.matches
:
277 text
= '%s at %s = %s' % (m
.type, m
.offset
, m
.value
)
279 text
+= ' masked with ' + m
.mask
280 iter = model
.append(parent
)
282 model
.set(iter, 0, text
, 1, m
)
284 model
.set(iter, 0, text
, 1, m
, 2, grey
)
286 build(self
.item
[1], parent
)
288 def add_edit_widgets(self
, vbox
):
290 g
.Label("The priority is from 0 (low) to 100 (high).\n"
291 "High priority matches take precedence over low ones."),
298 self
.adj
= g
.Adjustment(prio
, lower
= 0, upper
= 100, step_incr
= 1)
299 spinner
= g
.SpinButton(self
.adj
, 1, 0)
300 vbox
.pack_start(spinner
, False, True, 0)
301 spinner
.set_activates_default(True)
303 def __cmp__(self
, b
):
304 ret
= Field
.__cmp
__(self
, b
)
306 return cmp(self
.item
[1], b
.item
[1])
309 def get_blank_item(self
):
310 return ('http://example.com', 'documentElement')
313 return "<%s> with namespace '%s'" % (self
.item
[1], self
.item
[0])
315 class Other(Field
): pass
318 def __init__(self
, type, attrs
):
321 def start(self
, element
, attrs
): pass
322 def data(self
, data
): pass
325 class CommentParser(FieldParser
):
326 def __init__(self
, type, attrs
, user
):
327 FieldParser
.__init
__(self
, type, attrs
)
328 self
.lang
= attrs
.get(XML_NAMESPACE
+ ' lang', None)
332 def data(self
, data
):
336 self
.type.add_comment(self
.lang
, self
.comment
, self
.user
)
339 def __init__(self
, parent
, user
):
344 def __cmp__(self
, b
):
346 for x
, y
in zip(self
.matches
, b
.matches
):
354 return cmp(self
.type, b
.type) or cmp(self
.offset
, b
.offset
) or \
355 cmp(self
.value
, b
.value
) or cmp(self
.mask
, b
.mask
) or \
359 rox
.alert("Editing of matches isn't currently supported")
361 class MagicParser(FieldParser
):
362 def __init__(self
, type, attrs
, user
):
363 FieldParser
.__init
__(self
, type, attrs
)
364 self
.prio
= attrs
.get('priority', 50)
365 self
.match
= Match(None, user
)
368 def start(self
, element
, attrs
):
369 new
= Match(self
.match
, self
.user
)
370 new
.offset
= attrs
.get('offset', '?')
371 new
.type = attrs
.get('type', '?')
372 new
.value
= attrs
.get('value', '?')
373 new
.mask
= attrs
.get('mask', None)
374 self
.match
.matches
.append(new
)
377 if self
.match
.parent
:
378 self
.match
= self
.match
.parent
380 self
.type.add_magic(self
.prio
, self
.match
, self
.user
)
388 def parse(self
, path
, user
):
389 parser
= expat
.ParserCreate(namespace_separator
= ' ')
390 parser
.StartElementHandler
= self
.start
391 parser
.EndElementHandler
= self
.end
392 parser
.CharacterDataHandler
= self
.data
394 parser
.ParseFile(file(path
))
396 def start(self
, element
, attrs
):
399 assert element
== FREE_NS
+ ' mime-info'
400 elif self
.level
== 2:
401 assert element
== FREE_NS
+ ' mime-type'
402 self
.type = get_type(attrs
['type'])
403 elif self
.level
== 3:
404 if element
== FREE_NS
+ ' comment':
405 self
.handler
= CommentParser(self
.type, attrs
,
407 elif element
== FREE_NS
+ ' glob':
408 self
.type.add_glob(attrs
['pattern'], self
.user
)
409 elif element
== FREE_NS
+ ' magic':
410 self
.handler
= MagicParser(self
.type, attrs
,
412 elif element
== FREE_NS
+ ' root-XML':
413 self
.type.add_xml(attrs
['namespaceURI'],
417 self
.type.add_other(element
, self
.user
)
420 self
.handler
.start(element
, attrs
)
422 def end(self
, element
):
428 elif self
.level
== 2:
431 def data(self
, data
):
433 self
.handler
.data(data
)
435 def scan_file(path
, user
):
436 if not path
.endswith('.xml'): return
437 if not os
.path
.exists(path
):
441 scanner
.parse(path
, user
)
443 rox
.report_exception()
446 from xml
.dom
import minidom
447 if os
.path
.exists(user_override
):
448 doc
= minidom
.parse(user_override
)
450 doc
= minidom
.Document()
451 node
= doc
.createElementNS(FREE_NS
, 'mime-info')
452 doc
.appendChild(node
)
453 node
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', FREE_NS
)
456 def get_override_type(type_name
):
458 root
= doc
.documentElement
459 for c
in root
.childNodes
:
460 if c
.nodeType
!= Node
.ELEMENT_NODE
: continue
461 if c
.localName
== 'mime-type' and c
.namespaceURI
== FREE_NS
:
462 if c
.getAttributeNS(None, 'type') == type_name
:
464 node
= doc
.createElementNS(FREE_NS
, 'mime-type')
465 node
.setAttributeNS(None, 'type', type_name
)
466 root
.appendChild(node
)
469 def write_override(doc
):
470 home_packages
= os
.path
.join(home_mime
, 'packages')
471 if not os
.path
.isdir(home_packages
):
472 os
.makedirs(home_packages
)
473 path
= os
.path
.join(home_packages
, 'Override.xml.new')
474 doc
.writexml(file(path
, 'w'))
475 os
.rename(path
, path
[:-4])
484 os
.execlp('update-mime-database', 'update-mime-database', home_mime
)
488 rox
.info(os
.fdopen(r
, 'r').read())
492 __main__
.box
.update()
497 root
= doc
.documentElement
498 type_node
= doc
.createElementNS(FREE_NS
, 'mime-type')
499 root
.appendChild(type_node
)
501 type_node
.setAttributeNS(None, 'type', name
)
505 __main__
.box
.show_type(name
)
507 def delete_type(name
):
510 for c
in doc
.documentElement
.childNodes
:
511 if c
.nodeType
!= Node
.ELEMENT_NODE
: continue
512 if c
.nodeName
!= 'mime-type': continue
513 if c
.namespaceURI
!= FREE_NS
: continue
514 if c
.getAttributeNS(None, 'type') != name
: continue
515 doc
.documentElement
.removeChild(c
)
518 rox
.alert("No user-provided information about this type -- can't remove anything")
520 if not rox
.confirm("Really remove all user-specified information about type %s?" % name
,
525 # Type names defined even without Override.xml
529 "(Re)read the database."
530 global types
, system_types
531 if system_types
is None:
533 for mime_dir
in ['/usr/share/mime', '/usr/local/share/mime', home_mime
]:
534 packages_dir
= os
.path
.join(mime_dir
, 'packages')
535 if not os
.path
.isdir(packages_dir
):
537 packages
= os
.listdir(packages_dir
)
539 for package
in packages
:
540 if package
== 'Override.xml' and mime_dir
is home_mime
: continue
541 scan_file(os
.path
.join(packages_dir
, package
), False)
542 system_types
= types
.keys()
544 for t
in types
.keys():
545 if t
not in system_types
:
547 for t
in types
.values():
549 scan_file(user_override
, True)