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 _, zerostore
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, basestring
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
43 """@rtype: str"""
44 return self.id
45
56
57 - def get_path(self, stores, missing_ok = False):
58 """Return the root directory of this implementation.
59 For local implementations, this is L{local_path}.
60 For cached implementations, this is the directory in the cache.
61 @param stores: stores to search
62 @type stores: L{zerostore.Stores}
63 @param missing_ok: return None for uncached implementations
64 @type missing_ok: bool
65 @return: the path of the directory
66 @rtype: str | None
67 @since: 1.8"""
68 if self.local_path is not None:
69 return self.local_path
70 if not self.digests:
71
72 raise model.SafeException("No digests for {feed} {version}".format(feed = self.feed, version = self.version))
73 if missing_ok:
74 return stores.lookup_maybe(self.digests)
75 else:
76 return stores.lookup_any(self.digests)
77
79 """A Selection created from an Implementation"""
80
81 __slots__ = ['impl', 'dependencies', 'attrs', '_used_commands']
82
83 - def __init__(self, iface_uri, impl, dependencies):
84 """@type iface_uri: str
85 @type impl: L{zeroinstall.injector.model.Implementation}
86 @type dependencies: [L{zeroinstall.injector.model.Dependency}]"""
87 assert impl
88 self.impl = impl
89 self.dependencies = dependencies
90 self._used_commands = {}
91
92 attrs = impl.metadata.copy()
93 attrs['id'] = impl.id
94 attrs['version'] = impl.get_version()
95 attrs['interface'] = iface_uri
96 attrs['from-feed'] = impl.feed.url
97 if impl.local_path:
98 attrs['local-path'] = impl.local_path
99 self.attrs = attrs
100
101 @property
103
104 @property
106
108 assert name in self._used_commands, "internal error: '{command}' not in my commands list".format(command = name)
109 return self._used_commands[name]
110
112 return self._used_commands
113
115 """A Selection created by reading an XML selections document.
116 @ivar digests: a list of manifest digests
117 @type digests: [str]
118 """
119 __slots__ = ['bindings', 'dependencies', 'attrs', 'digests', 'commands']
120
121 - def __init__(self, dependencies, bindings = None, attrs = None, digests = None, commands = None):
139
146
148 """@rtype: {str: L{Command}}"""
149 return self.commands
150
152 """
153 A selected set of components which will make up a complete program.
154 @ivar interface: the interface of the program
155 @type interface: str
156 @ivar command: the command to run on 'interface'
157 @type command: str
158 @ivar selections: the selected implementations
159 @type selections: {str: L{Selection}}
160 """
161 __slots__ = ['interface', 'selections', 'command']
162
164 """Constructor.
165 @param source: a map of implementations, policy or selections document
166 @type source: L{Element}"""
167 self.selections = {}
168 self.command = None
169
170 if source is None:
171
172 pass
173 elif isinstance(source, Element):
174 self._init_from_qdom(source)
175 else:
176 raise Exception(_("Source not a qdom.Element!"))
177
179 """Parse and load a selections document.
180 @param root: a saved set of selections.
181 @type root: L{Element}"""
182 self.interface = root.getAttribute('interface')
183 self.command = root.getAttribute('command')
184 if self.interface is None:
185 raise model.SafeException(_("Not a selections document (no 'interface' attribute on root)"))
186 old_commands = []
187
188 for selection in root.childNodes:
189 if selection.uri != XMLNS_IFACE:
190 continue
191 if selection.name != 'selection':
192 if selection.name == 'command':
193 old_commands.append(Command(selection, None))
194 continue
195
196 requires = []
197 bindings = []
198 digests = []
199 commands = {}
200 for elem in selection.childNodes:
201 if elem.uri != XMLNS_IFACE:
202 continue
203 if elem.name in binding_names:
204 bindings.append(process_binding(elem))
205 elif elem.name == 'requires':
206 dep = process_depends(elem, None)
207 requires.append(dep)
208 elif elem.name == 'manifest-digest':
209 for aname, avalue in elem.attrs.items():
210 digests.append(zerostore.format_algorithm_digest_pair(aname, avalue))
211 elif elem.name == 'command':
212 name = elem.getAttribute('name')
213 assert name, "Missing name attribute on <command>"
214 commands[name] = Command(elem, None)
215
216
217 sel_id = selection.attrs['id']
218 local_path = selection.attrs.get("local-path", None)
219 if (not digests and not local_path) and '=' in sel_id:
220 alg = sel_id.split('=', 1)[0]
221 if alg in ('sha1', 'sha1new', 'sha256'):
222 digests.append(sel_id)
223
224 iface_uri = selection.attrs['interface']
225
226 s = XMLSelection(requires, bindings, selection.attrs, digests, commands)
227 self.selections[iface_uri] = s
228
229 if self.command is None:
230
231 if old_commands:
232
233 self.command = 'run'
234 iface = self.interface
235
236 for command in old_commands:
237 command.qdom.attrs['name'] = 'run'
238 self.selections[iface].commands['run'] = command
239 runner = command.get_runner()
240 if runner:
241 iface = runner.interface
242 else:
243 iface = None
244 else:
245
246 root_sel = self.selections[self.interface]
247 main = root_sel.attrs.get('main', None)
248 if main is not None:
249 root_sel.commands['run'] = Command(Element(XMLNS_IFACE, 'command', {'path': main, 'name': 'run'}), None)
250 self.command = 'run'
251
252 elif self.command == '':
253
254 self.command = None
255 assert not old_commands, "<command> list in new-style selections document!"
256
258 """Create a DOM document for the selected implementations.
259 The document gives the URI of the root, plus each selected implementation.
260 For each selected implementation, we record the ID, the version, the URI and
261 (if different) the feed URL. We also record all the bindings needed.
262 @return: a new DOM Document"""
263 from xml.dom import minidom, XMLNS_NAMESPACE
264
265 assert self.interface
266
267 impl = minidom.getDOMImplementation()
268
269 doc = impl.createDocument(XMLNS_IFACE, "selections", None)
270
271 root = doc.documentElement
272 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns', XMLNS_IFACE)
273
274 root.setAttributeNS(None, 'interface', self.interface)
275
276 root.setAttributeNS(None, 'command', self.command or "")
277
278 prefixes = Prefixes(XMLNS_IFACE)
279
280 for iface, selection in sorted(self.selections.items()):
281 selection_elem = doc.createElementNS(XMLNS_IFACE, 'selection')
282 selection_elem.setAttributeNS(None, 'interface', selection.interface)
283 root.appendChild(selection_elem)
284
285 for name, value in selection.attrs.items():
286 if ' ' in name:
287 ns, localName = name.split(' ', 1)
288 prefixes.setAttributeNS(selection_elem, ns, localName, value)
289 elif name == 'stability':
290 pass
291 elif name == 'from-feed':
292
293 if value != selection.attrs['interface']:
294 selection_elem.setAttributeNS(None, name, value)
295 elif name not in ('main', 'self-test'):
296 selection_elem.setAttributeNS(None, name, value)
297
298 if selection.digests:
299 manifest_digest = doc.createElementNS(XMLNS_IFACE, 'manifest-digest')
300 for digest in selection.digests:
301 aname, avalue = zerostore.parse_algorithm_digest_pair(digest)
302 assert ':' not in aname
303 manifest_digest.setAttribute(aname, avalue)
304 selection_elem.appendChild(manifest_digest)
305
306 for b in selection.bindings:
307 selection_elem.appendChild(b._toxml(doc, prefixes))
308
309 for dep in selection.dependencies:
310 if not isinstance(dep, model.InterfaceDependency): continue
311
312 dep_elem = doc.createElementNS(XMLNS_IFACE, 'requires')
313 dep_elem.setAttributeNS(None, 'interface', dep.interface)
314 selection_elem.appendChild(dep_elem)
315
316 for m in dep.metadata:
317 parts = m.split(' ', 1)
318 if len(parts) == 1:
319 ns = None
320 localName = parts[0]
321 dep_elem.setAttributeNS(None, localName, dep.metadata[m])
322 else:
323 ns, localName = parts
324 prefixes.setAttributeNS(dep_elem, ns, localName, dep.metadata[m])
325
326 for b in dep.bindings:
327 dep_elem.appendChild(b._toxml(doc, prefixes))
328
329 for command in selection.get_commands().values():
330 selection_elem.appendChild(command._toxml(doc, prefixes))
331
332 for ns, prefix in prefixes.prefixes.items():
333 root.setAttributeNS(XMLNS_NAMESPACE, 'xmlns:' + prefix, ns)
334
335 return doc
336
338 return "Selections for " + self.interface
339
341 """Find those selections which are not present.
342 Local implementations are available if their directory exists.
343 Other 0install implementations are available if they are in the cache.
344 Package implementations are available if the Distribution says so.
345 @param include_packages: whether to include <package-implementation>s
346 @type include_packages: bool
347 @rtype: [Selection]
348 @since: 1.16"""
349 iface_cache = config.iface_cache
350 stores = config.stores
351
352
353 def needs_download(sel):
354 if sel.id.startswith('package:'):
355 if not include_packages: return False
356 feed = iface_cache.get_feed(sel.feed)
357 if not feed: return False
358 impl = feed.implementations.get(sel.id, None)
359 return impl is None or not impl.installed
360 elif sel.local_path:
361 return False
362 else:
363 return sel.get_path(stores, missing_ok = True) is None
364
365 return [sel for sel in self.selections.values() if needs_download(sel)]
366
415 return download()
416
417
418
420
421 """@type key: str
422 @rtype: L{ImplSelection}"""
423 if isinstance(key, basestring):
424 return self.selections[key]
425 sel = self.selections[key.uri]
426 return sel and sel.impl
427
433
435
436 """@rtype: L{zeroinstall.injector.model.Implementation}"""
437 for (uri, sel) in self.selections.items():
438 yield sel and sel.impl
439
445
446 - def get(self, iface, if_missing):
447
448 """@type iface: L{zeroinstall.injector.model.Interface}
449 @rtype: L{zeroinstall.injector.model.Implementation}"""
450 sel = self.selections.get(iface.uri, None)
451 if sel:
452 return sel.impl
453 return if_missing
454
461
465
466 @property
485