1 # Copyright (C) 2006, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
8 from logging
import debug
, warn
9 from os
.path
import dirname
11 from zeroinstall
import version
12 from zeroinstall
.injector
import basedir
, qdom
13 from zeroinstall
.injector
.namespaces
import *
14 from zeroinstall
.injector
.model
import *
16 class InvalidInterface(SafeException
):
17 def __init__(self
, message
, ex
= None):
19 message
+= "\n\n(exact error: %s)" % ex
20 SafeException
.__init
__(self
, message
)
22 def get_singleton_text(parent
, ns
, localName
):
24 for x
in parent
.childNodes
:
25 if x
.uri
== ns
and x
.name
== localName
:
27 raise InvalidInterface('Multiple <%s> elements in <%s>' % (localName
, parent
.name
))
31 raise InvalidInterface('No <%s> element in <%s>' % (localName
, parent
.name
))
34 __slots__
= ['version', 'released', 'arch', 'stability', 'main']
35 def __init__(self
, **kwargs
):
36 for x
in self
.__slots
__:
37 setattr(self
, x
, kwargs
.get(x
, None))
39 def merge(self
, item
):
41 for x
in self
.__slots
__:
42 value
= item
.attrs
.get(x
, None)
44 value
= getattr(self
, x
)
45 setattr(new
, x
, value
)
48 def parse_version(version_string
):
49 if version_string
is None: return None
51 return map(int, version_string
.split('.'))
52 except ValueError, ex
:
53 raise InvalidInterface("Invalid version format in '%s': %s" % (version_string
, ex
))
55 def process_depends(dependency
, item
):
56 dependency
.min_version
= parse_version(item
.getAttribute('min-version'))
57 dependency
.max_version
= parse_version(item
.getAttribute('max-version'))
58 for e
in item
.childNodes
:
59 if e
.uri
== XMLNS_IFACE
and e
.name
== 'environment':
60 binding
= EnvironmentBinding(e
.getAttribute('name'),
61 insert
= e
.getAttribute('insert'),
62 default
= e
.getAttribute('default'))
63 dependency
.bindings
.append(binding
)
65 def update_from_cache(interface
):
66 """True if cached version and user overrides loaded OK.
67 False if upstream not cached. Local interfaces (starting with /) are
68 always considered to be cached, although they are not stored there."""
71 if interface
.uri
.startswith('/'):
72 debug("Loading local interface file '%s'", interface
.uri
)
73 update(interface
, interface
.uri
, local
= True)
74 interface
.last_modified
= os
.stat(interface
.uri
).st_mtime
77 cached
= basedir
.load_first_cache(config_site
, 'interfaces', escape(interface
.uri
))
79 debug("Loading cached information for %s from %s", interface
, cached
)
80 update(interface
, cached
)
82 update_user_overrides(interface
)
84 # Special case: add our fall-back local copy of the injector as a feed
85 if interface
.uri
== injector_gui_uri
:
86 local_gui
= os
.path
.join(os
.path
.abspath(dirname(dirname(__file__
))), '0launch-gui', 'injector-gui.xml')
87 interface
.feeds
.append(Feed(local_gui
, None, False))
91 def update_user_overrides(interface
):
92 user
= basedir
.load_first_config(config_site
, config_prog
,
93 'user_overrides', escape(interface
.uri
))
97 root
= qdom
.parse(file(user
))
99 last_checked
= root
.getAttribute('last-checked')
101 interface
.last_checked
= int(last_checked
)
103 stability_policy
= root
.getAttribute('stability-policy')
105 interface
.set_stability_policy(stability_levels
[str(stability_policy
)])
107 for item
in root
.childNodes
:
108 if item
.uri
!= XMLNS_IFACE
: continue
109 if item
.name
== 'implementation':
110 id = item
.getAttribute('id')
111 assert id is not None
112 if id.startswith('/'):
113 impl
= interface
.get_impl(id)
116 impl
= interface
.get_impl(id)
118 user_stability
= item
.getAttribute('user-stability')
120 impl
.user_stability
= stability_levels
[str(user_stability
)]
121 elif item
.name
== 'feed':
122 feed_src
= item
.getAttribute('src')
124 raise InvalidInterface('Missing "src" attribute in <feed>')
125 interface
.feeds
.append(Feed(feed_src
, item
.getAttribute('arch'), True))
127 def check_readable(interface_uri
, source
):
128 """Returns the modified time in 'source'. If syntax is incorrect,
129 throws an exception."""
130 tmp
= Interface(interface_uri
)
133 except InvalidInterface
, ex
:
134 raise InvalidInterface("Error loading interface:\n"
135 "Interface URI: %s\n"
136 "Local file: %s\n%s" %
137 (interface_uri
, source
, ex
))
138 return tmp
.last_modified
143 except Exception, ex
:
144 raise InvalidInterface("Date '%s' not in correct format (should be integer number "
145 "of seconds since Unix epoch)\n%s" % (t
, ex
))
147 def _check_canonical_name(interface
, source
, root
):
148 "Ensure the uri= attribute in the interface file matches the interface we are trying to load"
149 canonical_name
= root
.getAttribute('uri')
150 if not canonical_name
:
151 raise InvalidInterface("<interface> uri attribute missing in " + source
)
152 if canonical_name
!= interface
.uri
:
153 raise InvalidInterface("<interface> uri attribute is '%s', but accessed as '%s'\n(%s)" %
154 (canonical_name
, interface
.uri
, source
))
156 def update(interface
, source
, local
= False):
157 """local - use file mtime for last-modified, and uri attribute is ignored"""
158 assert isinstance(interface
, Interface
)
161 root
= qdom
.parse(file(source
))
162 except Exception, ex
:
163 raise InvalidInterface("Invalid XML", ex
)
166 _check_canonical_name(interface
, source
, root
)
167 time_str
= root
.getAttribute('last-modified')
169 raise InvalidInterface("Missing last-modified attribute on root element.")
170 interface
.last_modified
= parse_time(time_str
)
171 main
= root
.getAttribute('main')
173 interface
.main
= main
175 min_injector_version
= root
.getAttribute('min-injector-version')
176 if min_injector_version
:
178 min_ints
= map(int, min_injector_version
.split('.'))
179 except ValueError, ex
:
180 raise InvalidInterface("Bad version number '%s'" % min_injector_version
)
181 injector_version
= map(int, version
.split('.'))
182 if min_ints
> injector_version
:
183 raise InvalidInterface("This interface requires version %s or later of "
184 "the Zero Install injector, but I am only version %s. "
185 "You can get a newer version from http://0install.net" %
186 (min_injector_version
, version
))
189 iface_dir
= os
.path
.dirname(source
)
191 iface_dir
= None # Can't have relative paths
193 for x
in root
.childNodes
:
194 if x
.uri
!= XMLNS_IFACE
:
195 interface
.add_metadata(x
)
198 interface
.name
= interface
.name
or x
.content
199 elif x
.name
== 'description':
200 interface
.description
= interface
.description
or x
.content
201 elif x
.name
== 'summary':
202 interface
.summary
= interface
.summary
or x
.content
203 elif x
.name
== 'feed-for':
204 feed_iface
= x
.getAttribute('interface')
206 raise InvalidInterface('Missing "interface" attribute in <feed-for>')
207 interface
.feed_for
[feed_iface
] = True
208 elif x
.name
== 'feed':
209 feed_src
= x
.getAttribute('src')
211 raise InvalidInterface('Missing "src" attribute in <feed>')
212 if feed_src
.startswith('http:') or local
:
213 interface
.feeds
.append(Feed(feed_src
, x
.getAttribute('arch'), False))
215 raise InvalidInterface("Invalid feed URL '%s'" % feed_src
)
217 interface
.add_metadata(x
)
219 def process_group(group
, group_attrs
, base_depends
):
220 for item
in group
.childNodes
:
221 if item
.uri
!= XMLNS_IFACE
: continue
223 depends
= base_depends
.copy()
225 item_attrs
= group_attrs
.merge(item
)
227 for child
in item
.childNodes
:
228 if child
.uri
!= XMLNS_IFACE
: continue
229 if child
.name
== 'requires':
230 dep_iface
= child
.getAttribute('interface')
231 if dep_iface
is None:
232 raise InvalidInterface("Missing 'interface' on <requires>")
233 dep
= Dependency(dep_iface
)
234 process_depends(dep
, child
)
235 depends
[dep
.interface
] = dep
237 if item
.name
== 'group':
238 process_group(item
, item_attrs
, depends
)
239 elif item
.name
== 'implementation':
240 process_impl(item
, item_attrs
, depends
)
242 def process_impl(item
, item_attrs
, depends
):
243 id = item
.getAttribute('id')
245 raise InvalidInterface("Missing 'id' attribute on %s" % item
)
246 if local
and (id.startswith('/') or id.startswith('.')):
247 impl
= interface
.get_impl(os
.path
.abspath(os
.path
.join(iface_dir
, id)))
250 raise InvalidInterface('Invalid "id"; form is "alg=value" (got "%s")' % id)
251 alg
, sha1
= id.split('=')
254 except Exception, ex
:
255 raise InvalidInterface('Bad SHA1 attribute: %s' % ex
)
256 impl
= interface
.get_impl(id)
258 version
= item_attrs
.version
260 raise InvalidInterface("Missing version attribute")
261 impl
.version
= map(int, version
.split('.'))
263 impl
.main
= item_attrs
.main
265 if item_attrs
.released
:
266 impl
.released
= item_attrs
.released
268 size
= item
.getAttribute('size')
270 impl
.size
= long(size
)
271 impl
.arch
= item_attrs
.arch
273 stability
= stability_levels
[str(item_attrs
.stability
)]
275 stab
= str(item_attrs
.stability
)
276 if stab
!= stab
.lower():
277 raise InvalidInterface('Stability "%s" invalid - use lower case!' % item_attrs
.stability
)
278 raise InvalidInterface('Stability "%s" invalid' % item_attrs
.stability
)
279 if stability
>= preferred
:
280 raise InvalidInterface("Upstream can't set stability to preferred!")
281 impl
.upstream_stability
= stability
283 impl
.dependencies
.update(depends
)
285 for elem
in item
.childNodes
:
286 if elem
.uri
== XMLNS_IFACE
and elem
.name
== 'archive':
287 url
= elem
.getAttribute('href')
289 raise InvalidInterface("Missing href attribute on <archive>")
290 size
= elem
.getAttribute('size')
292 raise InvalidInterface("Missing size attribute on <archive>")
293 impl
.add_download_source(url
= url
, size
= long(size
),
294 extract
= elem
.getAttribute('extract'))
297 Attrs(stability
= testing
,
298 main
= root
.getAttribute('main') or None),