1 """
2 Load and save a set of chosen implementations.
3 @since: 0.27
4 """
5
6
7
8
9 import os
10 from zeroinstall import _
11 from zeroinstall.injector import model
12 from zeroinstall.injector.policy import get_deprecated_singleton_config
13 from zeroinstall.injector.model import process_binding, process_depends, binding_names, Command
14 from zeroinstall.injector.namespaces import XMLNS_IFACE
15 from zeroinstall.injector.qdom import Element, Prefixes
16 from zeroinstall.support import tasks
19 """A single selected implementation in a L{Selections} set.
20 @ivar dependencies: list of dependencies
21 @type dependencies: [L{model.Dependency}]
22 @ivar attrs: XML attributes map (name is in the format "{namespace} {localName}")
23 @type attrs: {str: str}
24 @ivar version: the implementation's version number
25 @type version: str"""
26
27 interface = property(lambda self: self.attrs['interface'])
28 id = property(lambda self: self.attrs['id'])
29 version = property(lambda self: self.attrs['version'])
30 feed = property(lambda self: self.attrs.get('from-feed', self.interface))
31 main = property(lambda self: self.attrs.get('main', None))
32
33 @property
41
44
55
57 """A Selection created from an Implementation"""
58
59 __slots__ = ['impl', 'dependencies', 'attrs', '_used_commands']
60
61 - def __init__(self, iface_uri, impl, dependencies):
75
76 @property
78
79 @property
81
85
91
93 """A Selection created by reading an XML selections document.
94 @ivar digests: a list of manifest digests
95 @type digests: [str]
96 """
97 __slots__ = ['bindings', 'dependencies', 'attrs', 'digests', 'commands']
98
99 - def __init__(self, dependencies, bindings = None, attrs = None, digests = None, commands = None):
112
117
120
122 """
123 A selected set of components which will make up a complete program.
124 @ivar interface: the interface of the program
125 @type interface: str
126 @ivar command: the command to run on 'interface'
127 @type command: str
128 @ivar selections: the selected implementations
129 @type selections: {str: L{Selection}}
130 """
131 __slots__ = ['interface', 'selections', 'command']
132
134 """Constructor.
135 @param source: a map of implementations, policy or selections document
136 @type source: L{Element}
137 """
138 self.selections = {}
139 self.command = None
140
141 if source is None:
142
143 pass
144 elif isinstance(source, Element):
145 self._init_from_qdom(source)
146 else:
147 raise Exception(_("Source not a qdom.Element!"))
148
150 """Parse and load a selections document.
151 @param root: a saved set of selections."""
152 self.interface = root.getAttribute('interface')
153 self.command = root.getAttribute('command')
154 assert self.interface
155 old_commands = []
156
157 for selection in root.childNodes:
158 if selection.uri != XMLNS_IFACE:
159 continue
160 if selection.name != 'selection':
161 if selection.name == 'command':
162 old_commands.append(Command(selection, None))
163 continue
164
165 requires = []
166 bindings = []
167 digests = []
168 commands = {}
169 for elem in selection.childNodes:
170 if elem.uri != XMLNS_IFACE:
171 continue
172 if elem.name in binding_names:
173 bindings.append(process_binding(elem))
174 elif elem.name == 'requires':
175 dep = process_depends(elem, None)
176 requires.append(dep)
177 elif elem.name == 'manifest-digest':
178 for aname, avalue in elem.attrs.iteritems():
179 digests.append('%s=%s' % (aname, avalue))
180 elif elem.name == 'command':
181 name = elem.getAttribute('name')
182 assert name, "Missing name attribute on <command>"
183 commands[name] = Command(elem, None)
184
185
186 sel_id = selection.attrs['id']
187 local_path = selection.attrs.get("local-path", None)
188 if (not digests and not local_path) and '=' in sel_id:
189 alg = sel_id.split('=', 1)[0]
190 if alg in ('sha1', 'sha1new', 'sha256'):
191 digests.append(sel_id)
192
193 iface_uri = selection.attrs['interface']
194
195 s = XMLSelection(requires, bindings, selection.attrs, digests, commands)
196 self.selections[iface_uri] = s
197
198 if self.command is None:
199
200 if old_commands:
201
202 self.command = 'run'
203 iface = self.interface
204
205 for command in old_commands:
206 command.qdom.attrs['name'] = 'run'
207 self.selections[iface].commands['run'] = command
208 runner = command.get_runner()
209 if runner:
210 iface = runner.interface
211 else:
212 iface = None
213 else:
214
215 root_sel = self.selections[self.interface]
216 main = root_sel.attrs.get('main', None)
217 if main is not None:
218 root_sel.commands['run'] = Command(Element(XMLNS_IFACE, 'command', {'path': main, 'name': 'run'}), None)
219 self.command = 'run'
220
221 elif self.command == '':
222
223 self.command = None
224 assert not old_commands, "<command> list in new-style selections document!"
225
227 """Create a DOM document for the selected implementations.
228 The document gives the URI of the root, plus each selected implementation.
229 For each selected implementation, we record the ID, the version, the URI and
230 (if different) the feed URL. We also record all the bindings needed.
231 @return: a new DOM Document"""
232 from xml.dom import minidom, XMLNS_NAMESPACE
233
234 assert self.interface
235
236 impl = minidom.getDOMImplementation()
237
238 doc = impl.createDocument(XMLNS_IFACE, "selections", None)
239
240 root = doc.documentElement
241 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns', XMLNS_IFACE)
242
243 root.setAttributeNS(None, 'interface', self.interface)
244
245 root.setAttributeNS(None, 'command', self.command or "")
246
247 prefixes = Prefixes(XMLNS_IFACE)
248
249 for iface, selection in sorted(self.selections.items()):
250 selection_elem = doc.createElementNS(XMLNS_IFACE, 'selection')
251 selection_elem.setAttributeNS(None, 'interface', selection.interface)
252 root.appendChild(selection_elem)
253
254 for name, value in selection.attrs.iteritems():
255 if ' ' in name:
256 ns, localName = name.split(' ', 1)
257 prefixes.setAttributeNS(selection_elem, ns, localName, value)
258 elif name == 'stability':
259 pass
260 elif name == 'from-feed':
261
262 if value != selection.attrs['interface']:
263 selection_elem.setAttributeNS(None, name, value)
264 elif name not in ('main', 'self-test'):
265 selection_elem.setAttributeNS(None, name, value)
266
267 if selection.digests:
268 manifest_digest = doc.createElementNS(XMLNS_IFACE, 'manifest-digest')
269 for digest in selection.digests:
270 aname, avalue = digest.split('=', 1)
271 assert ':' not in aname
272 manifest_digest.setAttribute(aname, avalue)
273 selection_elem.appendChild(manifest_digest)
274
275 for b in selection.bindings:
276 selection_elem.appendChild(b._toxml(doc, prefixes))
277
278 for dep in selection.dependencies:
279 dep_elem = doc.createElementNS(XMLNS_IFACE, 'requires')
280 dep_elem.setAttributeNS(None, 'interface', dep.interface)
281 selection_elem.appendChild(dep_elem)
282
283 for m in dep.metadata:
284 parts = m.split(' ', 1)
285 if len(parts) == 1:
286 ns = None
287 localName = parts[0]
288 dep_elem.setAttributeNS(None, localName, dep.metadata[m])
289 else:
290 ns, localName = parts
291 prefixes.setAttributeNS(dep_elem, ns, localName, dep.metadata[m])
292
293 for b in dep.bindings:
294 dep_elem.appendChild(b._toxml(doc, prefixes))
295
296 for command in selection.get_commands().values():
297 selection_elem.appendChild(command._toxml(doc, prefixes))
298
299 for ns, prefix in prefixes.prefixes.items():
300 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, ns)
301
302 return doc
303
305 return "Selections for " + self.interface
306
308 """Check all selected implementations are available.
309 Download any that are not present. Since native distribution packages are usually
310 only available in a single version, which is unlikely to be the one in the
311 selections document, we ignore them by default.
312 Note: package implementations (distribution packages) are ignored.
313 @param config: used to get iface_cache, stores and fetcher
314 @param include_packages: also try to install native packages (since 1.5)
315 @return: a L{tasks.Blocker} or None"""
316 from zeroinstall.zerostore import NotStored
317
318 if _old:
319 config = get_deprecated_singleton_config()
320
321 iface_cache = config.iface_cache
322 stores = config.stores
323
324
325 def needs_download(sel):
326 if sel.id.startswith('package:'):
327 if not include_packages: return False
328 feed = iface_cache.get_feed(sel.feed)
329 if not feed: return False
330 impl = feed.implementations.get(sel.id, None)
331 return impl is None or not impl.installed
332 elif sel.local_path:
333 return False
334 else:
335 try:
336 stores.lookup_any(sel.digests)
337 except NotStored:
338 return True
339
340 needed_downloads = list(filter(needs_download, self.selections.values()))
341 if not needed_downloads:
342 return
343
344 if config.network_use == model.network_offline:
345 from zeroinstall import NeedDownload
346 raise NeedDownload(', '.join([str(x) for x in needed_downloads]))
347
348 @tasks.async
349 def download():
350
351
352
353
354
355 needed_impls = []
356 for sel in needed_downloads:
357 feed_url = sel.attrs.get('from-feed', None) or sel.attrs['interface']
358 feed = iface_cache.get_feed(feed_url)
359 if feed is None or sel.id not in feed.implementations:
360 fetch_feed = config.fetcher.download_and_import_feed(feed_url, iface_cache)
361 yield fetch_feed
362 tasks.check(fetch_feed)
363
364 feed = iface_cache.get_feed(feed_url)
365 assert feed, "Failed to get feed for %s" % feed_url
366 impl = feed.implementations[sel.id]
367 needed_impls.append(impl)
368
369 fetch_impls = config.fetcher.download_impls(needed_impls, stores)
370 yield fetch_impls
371 tasks.check(fetch_impls)
372 return download()
373
374
375
377
378 if isinstance(key, basestring):
379 return self.selections[key]
380 sel = self.selections[key.uri]
381 return sel and sel.impl
382
388
393
399
400 - def get(self, iface, if_missing):
401
402 sel = self.selections.get(iface.uri, None)
403 if sel:
404 return sel.impl
405 return if_missing
406
413
417
418 @property
437