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
8 from logging
import debug
, warn
11 from namespaces
import *
14 from os
.path
import dirname
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
):
23 names
= parent
.getElementsByTagNameNS(ns
, localName
)
25 raise InvalidInterface('No <%s> element in <%s>' % (localName
, parent
.localName
))
27 raise InvalidInterface('Multiple <%s> elements in <%s>' % (localName
, parent
.localName
))
29 for x
in names
[0].childNodes
:
30 if x
.nodeType
== Node
.TEXT_NODE
:
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
):
42 for x
in self
.__slots
__:
43 if item
.hasAttribute(x
):
44 value
= item
.getAttribute(x
)
46 value
= getattr(self
, x
)
47 setattr(new
, x
, value
)
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."""
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
69 cached
= basedir
.load_first_cache(config_site
, 'interfaces', escape(interface
.uri
))
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))
83 def update_user_overrides(interface
):
84 user
= basedir
.load_first_config(config_site
, config_prog
,
85 'user_overrides', escape(interface
.uri
))
89 doc
= minidom
.parse(user
)
90 root
= doc
.documentElement
92 last_checked
= root
.getAttribute('last-checked')
94 interface
.last_checked
= int(last_checked
)
96 stability_policy
= root
.getAttribute('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)
107 impl
= interface
.get_impl(id)
109 user_stability
= item
.getAttribute('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')
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
)
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
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
)
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')
164 _check_canonical_name(interface
, source
, root
)
165 time_str
= root
.getAttribute('last-modified')
167 raise InvalidInterface("Missing last-modified attribute on root element.")
168 interface
.last_modified
= parse_time(time_str
)
169 main
= root
.getAttribute('main')
171 interface
.main
= main
174 iface_dir
= os
.path
.dirname(source
)
176 iface_dir
= None # Can't have relative paths
178 for source
in root
.getElementsByTagNameNS(XMLNS_IFACE
, 'source'):
179 source_interface
= source
.getAttribute('interface')
181 interface
.sources
.append(Source(source_interface
))
183 raise InvalidInterface("Missing interface attribute on <source>")
185 for feed
in root
.getElementsByTagNameNS(XMLNS_IFACE
, 'feed-for'):
186 feed_iface
= feed
.getAttribute('interface')
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')
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))
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
:
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')
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)))
226 raise InvalidInterface('Invalid "id"; form is "alg=value" (got "%s")' % id)
227 alg
, sha1
= id.split('=')
230 except Exception, ex
:
231 raise InvalidInterface('Bad SHA1 attribute: %s' % ex
)
232 impl
= interface
.get_impl(id)
234 version
= item_attrs
.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')
246 impl
.size
= long(size
)
247 impl
.arch
= item_attrs
.arch
249 stability
= stability_levels
[str(item_attrs
.stability
)]
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')
264 raise InvalidInterface("Missing href attribute on <archive>")
265 size
= elem
.getAttribute('size')
267 raise InvalidInterface("Missing size attribute on <archive>")
268 impl
.add_download_source(url
= url
, size
= long(size
),
269 extract
= elem
.getAttribute('extract'))
272 Attrs(stability
= testing
,
273 main
= root
.getAttribute('main') or None),