2 Load and save a set of chosen implementations.
6 # Copyright (C) 2009, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
9 from zeroinstall
import _
10 from zeroinstall
.injector
.policy
import Policy
11 from zeroinstall
.injector
.model
import process_binding
, process_depends
, binding_names
, Command
12 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
13 from zeroinstall
.injector
.qdom
import Element
, Prefixes
14 from zeroinstall
.support
import tasks
16 class Selection(object):
17 """A single selected implementation in a L{Selections} set.
18 @ivar dependencies: list of dependencies
19 @type dependencies: [L{model.Dependency}]
20 @ivar attrs: XML attributes map (name is in the format "{namespace} {localName}")
21 @type attrs: {str: str}
22 @ivar digests: a list of manifest digests
24 @ivar version: the implementation's version number
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))
35 local_path
= self
.attrs
.get('local-path', None)
38 if self
.id.startswith('/'):
45 class ImplSelection(Selection
):
46 __slots__
= ['impl', 'dependencies', 'attrs']
48 def __init__(self
, iface_uri
, impl
, dependencies
):
51 self
.dependencies
= dependencies
53 attrs
= impl
.metadata
.copy()
55 attrs
['version'] = impl
.get_version()
56 attrs
['interface'] = iface_uri
57 attrs
['from-feed'] = impl
.feed
.url
59 attrs
['local-path'] = impl
.local_path
63 def bindings(self
): return self
.impl
.bindings
66 def digests(self
): return self
.impl
.digests
68 class XMLSelection(Selection
):
69 __slots__
= ['bindings', 'dependencies', 'attrs', 'digests']
71 def __init__(self
, dependencies
, bindings
= None, attrs
= None, digests
= None):
72 if bindings
is None: bindings
= []
73 if digests
is None: digests
= []
74 self
.dependencies
= dependencies
75 self
.bindings
= bindings
77 self
.digests
= digests
84 class Selections(object):
86 A selected set of components which will make up a complete program.
87 @ivar interface: the interface of the program
89 @ivar commands: how to run this selection (will contain more than one item if runners are used)
90 @type commands: [{L{Command}}]
91 @ivar selections: the selected implementations
92 @type selections: {str: L{Selection}}
94 __slots__
= ['interface', 'selections', 'commands']
96 def __init__(self
, source
):
98 @param source: a map of implementations, policy or selections document
99 @type source: {str: L{Selection}} | L{Policy} | L{Element}
105 # (Solver will fill everything in)
106 elif isinstance(source
, Policy
):
107 self
._init
_from
_policy
(source
)
108 elif isinstance(source
, Element
):
109 self
._init
_from
_qdom
(source
)
111 raise Exception(_("Source not a Policy or qdom.Element!"))
113 def _init_from_policy(self
, policy
):
114 """Set the selections from a policy.
115 @deprecated: use Solver.selections instead
116 @param policy: the policy giving the selected implementations."""
117 self
.interface
= policy
.root
118 self
.selections
= policy
.solver
.selections
.selections
119 self
.commands
= policy
.solver
.selections
.commands
121 def _init_from_qdom(self
, root
):
122 """Parse and load a selections document.
123 @param root: a saved set of selections."""
124 self
.interface
= root
.getAttribute('interface')
125 assert self
.interface
128 for selection
in root
.childNodes
:
129 if selection
.uri
!= XMLNS_IFACE
:
131 if selection
.name
!= 'selection':
132 if selection
.name
== 'command':
133 self
.commands
.append(Command(selection
, None))
139 for dep_elem
in selection
.childNodes
:
140 if dep_elem
.uri
!= XMLNS_IFACE
:
142 if dep_elem
.name
in binding_names
:
143 bindings
.append(process_binding(dep_elem
))
144 elif dep_elem
.name
== 'requires':
145 dep
= process_depends(dep_elem
, None)
147 elif dep_elem
.name
== 'manifest-digest':
148 for aname
, avalue
in dep_elem
.attrs
.iteritems():
149 digests
.append('%s=%s' % (aname
, avalue
))
151 # For backwards compatibility, allow getting the digest from the ID
152 sel_id
= selection
.attrs
['id']
153 local_path
= selection
.attrs
.get("local-path", None)
154 if (not digests
and not local_path
) and '=' in sel_id
:
155 alg
= sel_id
.split('=', 1)[0]
156 if alg
in ('sha1', 'sha1new', 'sha256'):
157 digests
.append(sel_id
)
159 iface_uri
= selection
.attrs
['interface']
161 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
)
162 self
.selections
[iface_uri
] = s
164 if not self
.commands
:
165 # Old-style selections document; use the main attribute
166 if iface_uri
== self
.interface
:
167 root_sel
= self
.selections
[self
.interface
]
168 main
= root_sel
.attrs
.get('main', None)
170 self
.commands
= [Command(Element(XMLNS_IFACE
, 'command', {'path': main
}), None)]
173 """Create a DOM document for the selected implementations.
174 The document gives the URI of the root, plus each selected implementation.
175 For each selected implementation, we record the ID, the version, the URI and
176 (if different) the feed URL. We also record all the bindings needed.
177 @return: a new DOM Document"""
178 from xml
.dom
import minidom
, XMLNS_NAMESPACE
180 assert self
.interface
182 impl
= minidom
.getDOMImplementation()
184 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
186 root
= doc
.documentElement
187 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
189 root
.setAttributeNS(None, 'interface', self
.interface
)
191 prefixes
= Prefixes()
193 for iface
, selection
in sorted(self
.selections
.items()):
194 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
195 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
196 root
.appendChild(selection_elem
)
198 for name
, value
in selection
.attrs
.iteritems():
200 ns
, localName
= name
.split(' ', 1)
201 selection_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, value
)
202 elif name
== 'from-feed':
203 # Don't bother writing from-feed attr if it's the same as the interface
204 if value
!= selection
.attrs
['interface']:
205 selection_elem
.setAttributeNS(None, name
, value
)
206 elif name
not in ('main', 'self-test'): # (replaced by <command>)
207 selection_elem
.setAttributeNS(None, name
, value
)
209 if selection
.digests
:
210 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
211 for digest
in selection
.digests
:
212 aname
, avalue
= digest
.split('=', 1)
213 assert ':' not in aname
214 manifest_digest
.setAttribute(aname
, avalue
)
215 selection_elem
.appendChild(manifest_digest
)
217 for b
in selection
.bindings
:
218 selection_elem
.appendChild(b
._toxml
(doc
))
220 for dep
in selection
.dependencies
:
221 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
222 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
223 selection_elem
.appendChild(dep_elem
)
225 for m
in dep
.metadata
:
226 parts
= m
.split(' ', 1)
230 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
232 ns
, localName
= parts
233 dep_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, dep
.metadata
[m
])
235 for b
in dep
.bindings
:
236 dep_elem
.appendChild(b
._toxml
(doc
))
238 for command
in self
.commands
:
239 root
.appendChild(command
._toxml
(doc
, prefixes
))
241 for ns
, prefix
in prefixes
.prefixes
.items():
242 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
247 return "Selections for " + self
.interface
249 def download_missing(self
, iface_cache
, fetcher
):
250 """Check all selected implementations are available.
251 Download any that are not present.
252 Note: package implementations (distribution packages) are ignored.
253 @param iface_cache: cache to find feeds with download information
254 @param fetcher: used to download missing implementations
255 @return: a L{tasks.Blocker} or None"""
256 from zeroinstall
.zerostore
import NotStored
258 # Check that every required selection is cached
259 needed_downloads
= []
260 for sel
in self
.selections
.values():
261 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
263 iface_cache
.stores
.lookup_any(sel
.digests
)
264 except NotStored
, ex
:
265 needed_downloads
.append(sel
)
266 if not needed_downloads
:
271 # We're missing some. For each one, get the feed it came from
272 # and find the corresponding <implementation> in that. This will
273 # tell us where to get it from.
274 # Note: we look for an implementation with the same ID. Maybe we
275 # should check it has the same digest(s) too?
277 for sel
in needed_downloads
:
278 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
279 feed
= iface_cache
.get_feed(feed_url
)
280 if feed
is None or sel
.id not in feed
.implementations
:
281 fetch_feed
= fetcher
.download_and_import_feed(feed_url
, iface_cache
)
283 tasks
.check(fetch_feed
)
285 feed
= iface_cache
.get_feed(feed_url
)
286 assert feed
, "Failed to get feed for %s" % feed_url
287 impl
= feed
.implementations
[sel
.id]
288 needed_impls
.append(impl
)
290 fetch_impls
= fetcher
.download_impls(needed_impls
, iface_cache
.stores
)
292 tasks
.check(fetch_impls
)
295 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
297 def __getitem__(self
, key
):
299 if isinstance(key
, basestring
):
300 return self
.selections
[key
]
301 sel
= self
.selections
[key
.uri
]
302 return sel
and sel
.impl
306 from zeroinstall
.injector
.iface_cache
import iface_cache
307 for (uri
, sel
) in self
.selections
.iteritems():
308 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
312 from zeroinstall
.injector
.iface_cache
import iface_cache
313 for (uri
, sel
) in self
.selections
.iteritems():
314 yield sel
and sel
.impl
318 from zeroinstall
.injector
.iface_cache
import iface_cache
319 for (uri
, sel
) in self
.selections
.iteritems():
320 yield iface_cache
.get_interface(uri
)
322 def get(self
, iface
, if_missing
):
324 sel
= self
.selections
.get(iface
.uri
, None)
332 s
.interface
= self
.interface
333 s
.selections
= self
.selections
.copy()
338 return list(self
.iteritems())