Added some buttons and UI stuff. Non-functional.
[mime-editor.git] / type.py
blob893c8ff03af3717542479590b5c2f1c3946cd2aa
1 import os
2 import rox
3 from rox import g
4 from xml.parsers import expat
5 from xml.dom import XML_NAMESPACE
7 types = {}
9 home_mime = os.path.join(os.environ['HOME'], '.mime')
10 user_override = os.path.join(home_mime, 'packages', 'Override.xml')
12 FREE_NS='http://www.freedesktop.org/standards/shared-mime-info'
14 def get_type(name):
15 if name not in types:
16 types[name] = MIME_Type(name)
17 return types[name]
19 class MIME_Type:
20 def __init__(self, name):
21 assert name not in types
22 self.media, self.subtype = name.split('/')
24 self.comments = []
25 self.globs = []
26 self.magic = []
27 self.xml = []
28 self.others = []
30 def add_from_node(self, file):
31 self.sources.append(Source(file))
33 def add_comment(self, lang, comment):
34 self.comments.append((lang, comment))
36 def add_xml(self, uri, name):
37 self.xml.append((uri, name))
39 def add_magic(self, prio, root):
40 self.magic.append((prio, root))
42 def add_other(self, element):
43 self.others.append(element)
45 def add_glob(self, pattern):
46 self.globs.append(pattern)
48 def get_comment(self):
49 best = None
50 for lang, comment in self.comments:
51 if not lang:
52 return comment
53 best = comment
54 return best or self.get_name()
56 def get_name(self):
57 return self.media + '/' + self.subtype
59 def get_comments(self): return map(Comment, self.comments)
60 def get_globs(self): return map(Glob, self.globs)
61 def get_magic(self): return map(Magic, self.magic)
62 def get_xml(self): return map(XML, self.xml)
63 def get_others(self): return map(Other, self.others)
65 class Field:
66 "MIME_Type.get_* functions return a list of these."
67 def __init__(self, item):
68 self.item = item
70 def __str__(self):
71 return "<%s>" % self.item
73 def add_subtree(self, model, iter):
74 return
76 def __cmp__(self, b):
77 return cmp(str(self), str(b))
79 def edit(self):
80 rox.alert('Sorry, MIME-Editor does not support editing fields of this type')
82 def delete(self):
83 rox.alert('TODO')
85 def create(self):
86 rox.alert("Sorry, can't create fields of this type yet")
88 class Comment(Field):
89 def __str__(self):
90 lang, data = self.item
91 if lang:
92 lang = '[' + lang + '] '
93 else:
94 lang = '(default) '
95 return lang + data
97 class Glob(Field):
98 def __str__(self):
99 return "Match '%s'" % self.item
101 class Magic(Field):
102 def __str__(self):
103 prio, match = self.item
104 return "Match with priority %s" % prio
106 def add_subtree(self, model, parent):
107 def build(match, parent):
108 for m in match.matches:
109 text = '%s at %s = %s' % (m.type, m.offset, m.value)
110 if m.mask:
111 text += ' masked with ' + m.mask
112 iter = model.append(parent)
113 model.set(iter, 0, text, 1, m)
114 build(m, iter)
115 build(self.item[1], parent)
117 def __cmp__(self, b):
118 ret = cmp(str(self), str(b))
119 if ret: return ret
120 return cmp(self.item[1], b.item[1])
122 class XML(Field):
123 def __str__(self):
124 return "<%s> with namespace '%s'" % (self.item[1], self.item[0])
126 class Other(Field): pass
128 class FieldParser:
129 def __init__(self, type, attrs):
130 self.type = type
132 def start(self, element, attrs): pass
133 def data(self, data): pass
134 def end(self): pass
136 class CommentParser(FieldParser):
137 def __init__(self, type, attrs):
138 FieldParser.__init__(self, type, attrs)
139 self.lang = attrs.get(XML_NAMESPACE + ' lang', None)
140 self.comment = ''
142 def data(self, data):
143 self.comment += data
145 def end(self):
146 self.type.add_comment(self.lang, self.comment)
148 class Match:
149 def __init__(self, parent):
150 self.parent = parent
151 self.matches = []
153 def __cmp__(self, b):
154 def child_cmp():
155 for x, y in zip(self.matches, b.matches):
156 c = cmp(x, y)
157 if c: return c
158 return 0
160 if not self.parent:
161 return child_cmp()
163 return cmp(self.type, b.type) or cmp(self.offset, b.offset) or \
164 cmp(self.value, b.value) or cmp(self.mask, b.mask) or \
165 child_cmp()
167 def edit(self):
168 rox.alert("Editing of matches isn't currently supported")
170 class MagicParser(FieldParser):
171 def __init__(self, type, attrs):
172 FieldParser.__init__(self, type, attrs)
173 self.prio = attrs.get('priority', 50)
174 self.match = Match(None)
176 def start(self, element, attrs):
177 new = Match(self.match)
178 new.offset = attrs.get('offset', '?')
179 new.type = attrs.get('type', '?')
180 new.value = attrs.get('value', '?')
181 new.mask = attrs.get('mask', None)
182 self.match.matches.append(new)
184 def end(self):
185 if self.match.parent:
186 self.match = self.match.parent
187 else:
188 self.type.add_magic(self.prio, self.match)
190 class Scanner:
191 def __init__(self):
192 self.level = 0
193 self.type = None
194 self.handler = None
196 def parse(self, path):
197 parser = expat.ParserCreate(namespace_separator = ' ')
198 parser.StartElementHandler = self.start
199 parser.EndElementHandler = self.end
200 parser.CharacterDataHandler = self.data
201 parser.ParseFile(file(path))
203 def start(self, element, attrs):
204 self.level += 1
205 if self.level == 1:
206 assert element == FREE_NS + ' mime-info'
207 elif self.level == 2:
208 assert element == FREE_NS + ' mime-type'
209 self.type = get_type(attrs['type'])
210 elif self.level == 3:
211 if element == FREE_NS + ' comment':
212 self.handler = CommentParser(self.type, attrs)
213 elif element == FREE_NS + ' glob':
214 self.type.add_glob(attrs['pattern'])
215 elif element == FREE_NS + ' magic':
216 self.handler = MagicParser(self.type, attrs)
217 elif element == FREE_NS + ' root-XML':
218 self.type.add_xml(attrs['namespaceURI'], attrs['localName'])
219 else:
220 self.type.add_other(element)
221 else:
222 assert self.handler
223 self.handler.start(element, attrs)
225 def end(self, element):
226 if self.handler:
227 self.handler.end()
228 self.level -=1
229 if self.level == 1:
230 self.type = None
231 elif self.level == 2:
232 self.handler = None
234 def data(self, data):
235 if self.handler:
236 self.handler.data(data)
238 def scan_file(path):
239 if not path.endswith('.xml'): return
240 if not os.path.exists(path):
241 return
242 scanner = Scanner()
243 try:
244 scanner.parse(path)
245 except:
246 rox.report_exception()
248 def get_override():
249 from xml.dom import minidom
250 if os.path.exists(user_override):
251 doc = minidom.parse(user_override)
252 else:
253 doc = minidom.Document()
254 node = doc.createElementNS(FREE_NS, 'mime-info')
255 doc.appendChild(node)
256 node.setAttributeNS(XMLNS_NAMESPACE, 'xmlns', FREE_NS)
257 return doc
259 def write_override(doc):
260 home_packages = os.path.join(home_mime, 'packages')
261 if not os.path.isdir(home_packages):
262 os.makedirs(home_packages)
263 path = os.path.join(home_packages, 'Override.xml.new')
264 doc.writexml(file(path, 'w'))
265 os.rename(path, path[:-4])
266 r, w = os.pipe()
267 child = os.fork()
268 if child == 0:
269 # Child
270 try:
271 os.close(r)
272 os.dup2(w, 1)
273 os.dup2(w, 2)
274 os.execlp('update-mime-database', 'update-mime-database', home_mime)
275 finally:
276 os._exit(1)
277 os.close(w)
278 rox.info(os.fdopen(r, 'r').read())
279 os.waitpid(child, 0)
281 import __main__
282 __main__.box.update()
284 def add_type(name):
285 doc = get_override()
287 root = doc.documentElement
288 type_node = doc.createElementNS(FREE_NS, 'mime-type')
289 root.appendChild(type_node)
291 type_node.setAttributeNS(None, 'type', name)
293 write_override(doc)
295 def delete_type(name):
296 from xml.dom import Node
297 doc = get_override()
298 removed = False
299 for c in doc.documentElement.childNodes:
300 if c.nodeType != Node.ELEMENT_NODE: continue
301 if c.nodeName != 'mime-type': continue
302 if c.namespaceURI != FREE_NS: continue
303 if c.getAttributeNS(None, 'type') != name: continue
304 doc.documentElement.removeChild(c)
305 removed = True
306 if not removed:
307 rox.alert("No user-provided information about this type -- can't remove anything")
308 return
309 if not rox.confirm("Really remove all user-specified information about type %s?" % name,
310 g.STOCK_DELETE):
311 return
312 write_override(doc)
314 def init():
315 "(Re)read the database."
316 global types
317 types = {}
318 for mime_dir in ['/usr/share/mime', '/usr/local/share/mime', home_mime]:
319 packages_dir = os.path.join(mime_dir, 'packages')
320 if not os.path.isdir(packages_dir):
321 continue
322 packages = os.listdir(packages_dir)
323 packages.sort()
324 for package in packages:
325 if package == 'Override.xml' and mime_dir is home_mime: continue
326 scan_file(os.path.join(packages_dir, package))
327 scan_file(user_override)