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
12 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
13 from zeroinstall
.injector
.qdom
import Element
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 selections: the selected implementations
90 @type selections: {str: L{Selection}}
92 __slots__
= ['interface', 'selections']
94 def __init__(self
, source
):
96 @param source: a map of implementations, policy or selections document
97 @type source: {str: L{Selection}} | L{Policy} | L{Element}
103 elif isinstance(source
, Policy
):
104 self
._init
_from
_policy
(source
)
105 elif isinstance(source
, Element
):
106 self
._init
_from
_qdom
(source
)
108 raise Exception(_("Source not a Policy or qdom.Element!"))
110 def _init_from_policy(self
, policy
):
111 """Set the selections from a policy.
112 @deprecated: use Solver.selections instead
113 @param policy: the policy giving the selected implementations."""
114 self
.interface
= policy
.root
115 self
.selections
= policy
.solver
.selections
.selections
117 def _init_from_qdom(self
, root
):
118 """Parse and load a selections document.
119 @param root: a saved set of selections."""
120 self
.interface
= root
.getAttribute('interface')
121 assert self
.interface
123 for selection
in root
.childNodes
:
124 if selection
.uri
!= XMLNS_IFACE
:
126 if selection
.name
!= 'selection':
132 for dep_elem
in selection
.childNodes
:
133 if dep_elem
.uri
!= XMLNS_IFACE
:
135 if dep_elem
.name
in binding_names
:
136 bindings
.append(process_binding(dep_elem
))
137 elif dep_elem
.name
== 'requires':
138 dep
= process_depends(dep_elem
)
140 elif dep_elem
.name
== 'manifest-digest':
141 for aname
, avalue
in dep_elem
.attrs
.iteritems():
142 digests
.append('%s=%s' % (aname
, avalue
))
144 # For backwards compatibility, allow getting the digest from the ID
145 sel_id
= selection
.attrs
['id']
146 local_path
= selection
.attrs
.get("local-path", None)
147 if (not digests
and not local_path
) and '=' in sel_id
:
148 alg
= sel_id
.split('=', 1)[0]
149 if alg
in ('sha1', 'sha1new', 'sha256'):
150 digests
.append(sel_id
)
152 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
)
153 self
.selections
[selection
.attrs
['interface']] = s
156 """Create a DOM document for the selected implementations.
157 The document gives the URI of the root, plus each selected implementation.
158 For each selected implementation, we record the ID, the version, the URI and
159 (if different) the feed URL. We also record all the bindings needed.
160 @return: a new DOM Document"""
161 from xml
.dom
import minidom
, XMLNS_NAMESPACE
163 assert self
.interface
165 impl
= minidom
.getDOMImplementation()
167 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
169 root
= doc
.documentElement
170 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
172 root
.setAttributeNS(None, 'interface', self
.interface
)
174 def ensure_prefix(prefixes
, ns
):
175 prefix
= prefixes
.get(ns
, None)
178 prefix
= 'ns%d' % len(prefixes
)
179 prefixes
[ns
] = prefix
184 for iface
, selection
in sorted(self
.selections
.items()):
185 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
186 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
187 root
.appendChild(selection_elem
)
189 for name
, value
in selection
.attrs
.iteritems():
191 ns
, localName
= name
.split(' ', 1)
192 selection_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, value
)
193 elif name
!= 'from-feed':
194 selection_elem
.setAttributeNS(None, name
, value
)
195 elif value
!= selection
.attrs
['interface']:
196 # Don't bother writing from-feed attr if it's the same as the interface
197 selection_elem
.setAttributeNS(None, name
, value
)
199 if selection
.digests
:
200 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
201 for digest
in selection
.digests
:
202 aname
, avalue
= digest
.split('=', 1)
203 assert ':' not in aname
204 manifest_digest
.setAttribute(aname
, avalue
)
205 selection_elem
.appendChild(manifest_digest
)
207 for b
in selection
.bindings
:
208 selection_elem
.appendChild(b
._toxml
(doc
))
210 for dep
in selection
.dependencies
:
211 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
212 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
213 selection_elem
.appendChild(dep_elem
)
215 for m
in dep
.metadata
:
216 parts
= m
.split(' ', 1)
220 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
222 ns
, localName
= parts
223 dep_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, dep
.metadata
[m
])
225 for b
in dep
.bindings
:
226 dep_elem
.appendChild(b
._toxml
(doc
))
228 for ns
, prefix
in prefixes
.items():
229 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
234 return "Selections for " + self
.interface
236 def download_missing(self
, iface_cache
, fetcher
):
237 """Check all selected implementations are available.
238 Download any that are not present.
239 Note: package implementations (distribution packages) are ignored.
240 @param iface_cache: cache to find feeds with download information
241 @param fetcher: used to download missing implementations
242 @return: a L{tasks.Blocker} or None"""
243 from zeroinstall
.zerostore
import NotStored
245 # Check that every required selection is cached
246 needed_downloads
= []
247 for sel
in self
.selections
.values():
248 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
250 iface_cache
.stores
.lookup_any(sel
.digests
)
251 except NotStored
, ex
:
252 needed_downloads
.append(sel
)
253 if not needed_downloads
:
258 # We're missing some. For each one, get the feed it came from
259 # and find the corresponding <implementation> in that. This will
260 # tell us where to get it from.
261 # Note: we look for an implementation with the same ID. Maybe we
262 # should check it has the same digest(s) too?
264 for sel
in needed_downloads
:
265 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
266 feed
= iface_cache
.get_feed(feed_url
)
267 if feed
is None or sel
.id not in feed
.implementations
:
268 fetch_feed
= fetcher
.download_and_import_feed(feed_url
, iface_cache
)
270 tasks
.check(fetch_feed
)
272 feed
= iface_cache
.get_feed(feed_url
)
273 assert feed
, "Failed to get feed for %s" % feed_url
274 impl
= feed
.implementations
[sel
.id]
275 needed_impls
.append(impl
)
277 fetch_impls
= fetcher
.download_impls(needed_impls
, iface_cache
.stores
)
279 tasks
.check(fetch_impls
)
282 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
284 def __getitem__(self
, key
):
286 if isinstance(key
, basestring
):
287 return self
.selections
[key
]
288 sel
= self
.selections
[key
.uri
]
289 return sel
and sel
.impl
293 from zeroinstall
.injector
.iface_cache
import iface_cache
294 for (uri
, sel
) in self
.selections
.iteritems():
295 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
299 from zeroinstall
.injector
.iface_cache
import iface_cache
300 for (uri
, sel
) in self
.selections
.iteritems():
301 yield sel
and sel
.impl
305 from zeroinstall
.injector
.iface_cache
import iface_cache
306 for (uri
, sel
) in self
.selections
.iteritems():
307 yield iface_cache
.get_interface(uri
)
309 def get(self
, iface
, if_missing
):
311 sel
= self
.selections
.get(iface
.uri
, None)
319 s
.interface
= self
.interface
320 s
.selections
= self
.selections
.copy()
325 return list(self
.iteritems())