When switching to GUI mode, don't even try to download the GUI interface; just run...
[zeroinstall.git] / zeroinstall / injector / reader.py
blobeda0faf5bed93ca1dcf415518d29f5973756e710
1 # Copyright (C) 2006, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
4 from xml.dom import Node, minidom
5 import sys
6 import shutil
7 import time
8 from logging import debug, warn
10 import basedir
11 from namespaces import *
12 from model import *
13 import os
14 from os.path import dirname
16 class InvalidInterface(SafeException):
17 def __init__(self, message, ex = None):
18 if ex:
19 message += "\n\n(exact error: %s)" % ex
20 SafeException.__init__(self, message)
22 def get_singleton_text(parent, ns, localName):
23 names = parent.getElementsByTagNameNS(ns, localName)
24 if not names:
25 raise InvalidInterface('No <%s> element in <%s>' % (localName, parent.localName))
26 if len(names) > 1:
27 raise InvalidInterface('Multiple <%s> elements in <%s>' % (localName, parent.localName))
28 text = ''
29 for x in names[0].childNodes:
30 if x.nodeType == Node.TEXT_NODE:
31 text += x.data
32 return text.strip()
34 class Attrs(object):
35 __slots__ = ['version', 'released', 'arch', 'stability', 'main']
36 def __init__(self, **kwargs):
37 for x in self.__slots__:
38 setattr(self, x, kwargs.get(x, None))
40 def merge(self, item):
41 new = Attrs()
42 for x in self.__slots__:
43 if item.hasAttribute(x):
44 value = item.getAttribute(x)
45 else:
46 value = getattr(self, x)
47 setattr(new, x, value)
48 return new
50 def process_depends(dependency, item):
51 for e in item.getElementsByTagNameNS(XMLNS_IFACE, 'environment'):
52 binding = EnvironmentBinding(e.getAttribute('name'),
53 insert = e.getAttribute('insert'),
54 default = e.getAttribute('default'))
55 dependency.bindings.append(binding)
57 def update_from_cache(interface):
58 """True if cached version and user overrides loaded OK.
59 False if upstream not cached. Local interfaces (starting with /) are
60 always considered to be cached, although they are not stored there."""
61 interface.reset()
63 if interface.uri.startswith('/'):
64 debug("Loading local interface file '%s'", interface.uri)
65 update(interface, interface.uri, local = True)
66 interface.last_modified = os.stat(interface.uri).st_mtime
67 cached = True
68 else:
69 cached = basedir.load_first_cache(config_site, 'interfaces', escape(interface.uri))
70 if cached:
71 debug("Loading cached information for %s from %s", interface, cached)
72 update(interface, cached)
74 update_user_overrides(interface)
76 # Special case: add our fall-back local copy of the injector as a feed
77 if interface.uri == injector_gui_uri:
78 local_gui = os.path.join(os.path.abspath(dirname(dirname(__file__))), '0launch-gui', 'injector-gui.xml')
79 interface.feeds.append(Feed(local_gui, None, False))
81 return bool(cached)
83 def update_user_overrides(interface):
84 user = basedir.load_first_config(config_site, config_prog,
85 'user_overrides', escape(interface.uri))
86 if not user:
87 return
89 doc = minidom.parse(user)
90 root = doc.documentElement
92 last_checked = root.getAttribute('last-checked')
93 if last_checked:
94 interface.last_checked = int(last_checked)
96 stability_policy = root.getAttribute('stability-policy')
97 if stability_policy:
98 interface.set_stability_policy(stability_levels[str(stability_policy)])
100 for item in root.getElementsByTagNameNS(XMLNS_IFACE, 'implementation'):
101 id = item.getAttribute('id')
102 assert id is not None
103 if id.startswith('/'):
104 impl = interface.get_impl(id)
105 else:
106 assert '=' in id
107 impl = interface.get_impl(id)
109 user_stability = item.getAttribute('user-stability')
110 if user_stability:
111 impl.user_stability = stability_levels[str(user_stability)]
113 for feed in root.getElementsByTagNameNS(XMLNS_IFACE, 'feed'):
114 feed_src = feed.getAttribute('src')
115 if not feed_src:
116 raise InvalidInterface('Missing "src" attribute in <feed>')
117 interface.feeds.append(Feed(feed_src, feed.getAttribute('arch'), True))
119 def check_readable(interface_uri, source):
120 """Returns the modified time in 'source'. If syntax is incorrect,
121 throws an exception."""
122 tmp = Interface(interface_uri)
123 try:
124 update(tmp, source)
125 except InvalidInterface, ex:
126 raise InvalidInterface("Error loading interface:\n"
127 "Interface URI: %s\n"
128 "Local file: %s\n%s" %
129 (interface_uri, source, ex))
130 return tmp.last_modified
132 def parse_time(t):
133 try:
134 return long(t)
135 except Exception, ex:
136 raise InvalidInterface("Date '%s' not in correct format (should be integer number "
137 "of seconds since Unix epoch)\n%s" % (t, ex))
139 def _check_canonical_name(interface, source, root):
140 "Ensure the uri= attribute in the interface file matches the interface we are trying to load"
141 canonical_name = root.getAttribute('uri')
142 if not canonical_name:
143 raise InvalidInterface("<interface> uri attribute missing in " + source)
144 if canonical_name != interface.uri:
145 raise InvalidInterface("<interface> uri attribute is '%s', but accessed as '%s'\n(%s)" %
146 (canonical_name, interface.uri, source))
148 def update(interface, source, local = False):
149 """local - use file mtime for last-modified, and uri attribute is ignored"""
150 assert isinstance(interface, Interface)
152 try:
153 doc = minidom.parse(source)
154 except Exception, ex:
155 raise InvalidInterface("Invalid XML", ex)
157 root = doc.documentElement
159 interface.name = interface.name or get_singleton_text(root, XMLNS_IFACE, 'name')
160 interface.description = interface.description or get_singleton_text(root, XMLNS_IFACE, 'description')
161 interface.summary = interface.summary or get_singleton_text(root, XMLNS_IFACE, 'summary')
163 if not local:
164 _check_canonical_name(interface, source, root)
165 time_str = root.getAttribute('last-modified')
166 if not time_str:
167 raise InvalidInterface("Missing last-modified attribute on root element.")
168 interface.last_modified = parse_time(time_str)
169 main = root.getAttribute('main')
170 if main:
171 interface.main = main
173 if local:
174 iface_dir = os.path.dirname(source)
175 else:
176 iface_dir = None # Can't have relative paths
178 for source in root.getElementsByTagNameNS(XMLNS_IFACE, 'source'):
179 source_interface = source.getAttribute('interface')
180 if source_interface:
181 interface.sources.append(Source(source_interface))
182 else:
183 raise InvalidInterface("Missing interface attribute on <source>")
185 for feed in root.getElementsByTagNameNS(XMLNS_IFACE, 'feed-for'):
186 feed_iface = feed.getAttribute('interface')
187 if not feed_iface:
188 raise InvalidInterface('Missing "interface" attribute in <feed-for>')
189 interface.feed_for[feed_iface] = True
191 for feed in root.getElementsByTagNameNS(XMLNS_IFACE, 'feed'):
192 feed_src = feed.getAttribute('src')
193 if not feed_src:
194 raise InvalidInterface('Missing "src" attribute in <feed>')
195 if feed_src.startswith('http:') or local:
196 interface.feeds.append(Feed(feed_src, feed.getAttribute('arch'), False))
197 else:
198 raise InvalidInterface("Invalid feed URL '%s'" % feed_src)
200 def process_group(group, group_attrs, base_depends):
201 for item in group.childNodes:
202 depends = base_depends.copy()
203 if item.namespaceURI != XMLNS_IFACE:
204 continue
206 item_attrs = group_attrs.merge(item)
208 for dep_elem in item.getElementsByTagNameNS(XMLNS_IFACE, 'requires'):
209 dep = Dependency(dep_elem.getAttribute('interface'))
210 process_depends(dep, dep_elem)
211 depends[dep.interface] = dep
213 if item.localName == 'group':
214 process_group(item, item_attrs, depends)
215 elif item.localName == 'implementation':
216 process_impl(item, item_attrs, depends)
218 def process_impl(item, item_attrs, depends):
219 id = item.getAttribute('id')
220 if id is None:
221 raise InvalidInterface("Missing 'id' attribute on %s" % item)
222 if local and (id.startswith('/') or id.startswith('.')):
223 impl = interface.get_impl(os.path.abspath(os.path.join(iface_dir, id)))
224 else:
225 if '=' not in id:
226 raise InvalidInterface('Invalid "id"; form is "alg=value" (got "%s")' % id)
227 alg, sha1 = id.split('=')
228 try:
229 long(sha1, 16)
230 except Exception, ex:
231 raise InvalidInterface('Bad SHA1 attribute: %s' % ex)
232 impl = interface.get_impl(id)
234 version = item_attrs.version
235 if not version:
236 raise InvalidInterface("Missing version attribute")
237 impl.version = map(int, version.split('.'))
239 impl.main = item_attrs.main
241 if item_attrs.released:
242 impl.released = item_attrs.released
244 size = item.getAttribute('size')
245 if size:
246 impl.size = long(size)
247 impl.arch = item_attrs.arch
248 try:
249 stability = stability_levels[str(item_attrs.stability)]
250 except KeyError:
251 stab = str(item_attrs.stability)
252 if stab != stab.lower():
253 raise InvalidInterface('Stability "%s" invalid - use lower case!' % item_attrs.stability)
254 raise InvalidInterface('Stability "%s" invalid' % item_attrs.stability)
255 if stability >= preferred:
256 raise InvalidInterface("Upstream can't set stability to preferred!")
257 impl.upstream_stability = stability
259 impl.dependencies.update(depends)
261 for elem in item.getElementsByTagNameNS(XMLNS_IFACE, 'archive'):
262 url = elem.getAttribute('href')
263 if not url:
264 raise InvalidInterface("Missing href attribute on <archive>")
265 size = elem.getAttribute('size')
266 if not size:
267 raise InvalidInterface("Missing size attribute on <archive>")
268 impl.add_download_source(url = url, size = long(size),
269 extract = elem.getAttribute('extract'))
271 process_group(root,
272 Attrs(stability = testing,
273 main = root.getAttribute('main') or None),