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 command: how to run this selection
90 @type command: {L{Command}}
91 @ivar selections: the selected implementations
92 @type selections: {str: L{Selection}}
94 __slots__
= ['interface', 'selections', 'command']
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 elif isinstance(source
, Policy
):
106 self
._init
_from
_policy
(source
)
107 elif isinstance(source
, Element
):
108 self
._init
_from
_qdom
(source
)
110 raise Exception(_("Source not a Policy or qdom.Element!"))
112 def _init_from_policy(self
, policy
):
113 """Set the selections from a policy.
114 @deprecated: use Solver.selections instead
115 @param policy: the policy giving the selected implementations."""
116 self
.interface
= policy
.root
117 self
.selections
= policy
.solver
.selections
.selections
118 self
.command
= policy
.solver
.selections
.command
120 def _init_from_qdom(self
, root
):
121 """Parse and load a selections document.
122 @param root: a saved set of selections."""
123 self
.interface
= root
.getAttribute('interface')
124 assert self
.interface
127 for selection
in root
.childNodes
:
128 if selection
.uri
!= XMLNS_IFACE
:
130 if selection
.name
!= 'selection':
131 if selection
.name
== 'command':
132 self
.command
= Command(selection
)
138 for dep_elem
in selection
.childNodes
:
139 if dep_elem
.uri
!= XMLNS_IFACE
:
141 if dep_elem
.name
in binding_names
:
142 bindings
.append(process_binding(dep_elem
))
143 elif dep_elem
.name
== 'requires':
144 dep
= process_depends(dep_elem
)
146 elif dep_elem
.name
== 'manifest-digest':
147 for aname
, avalue
in dep_elem
.attrs
.iteritems():
148 digests
.append('%s=%s' % (aname
, avalue
))
150 # For backwards compatibility, allow getting the digest from the ID
151 sel_id
= selection
.attrs
['id']
152 local_path
= selection
.attrs
.get("local-path", None)
153 if (not digests
and not local_path
) and '=' in sel_id
:
154 alg
= sel_id
.split('=', 1)[0]
155 if alg
in ('sha1', 'sha1new', 'sha256'):
156 digests
.append(sel_id
)
158 iface_uri
= selection
.attrs
['interface']
159 if iface_uri
== self
.interface
:
160 main
= selection
.attrs
.get('main', None)
162 self
.command
= Command(Element(XMLNS_IFACE
, 'command', {'path': main
}))
164 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
)
165 self
.selections
[iface_uri
] = s
168 """Create a DOM document for the selected implementations.
169 The document gives the URI of the root, plus each selected implementation.
170 For each selected implementation, we record the ID, the version, the URI and
171 (if different) the feed URL. We also record all the bindings needed.
172 @return: a new DOM Document"""
173 from xml
.dom
import minidom
, XMLNS_NAMESPACE
175 assert self
.interface
177 impl
= minidom
.getDOMImplementation()
179 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
181 root
= doc
.documentElement
182 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
184 root
.setAttributeNS(None, 'interface', self
.interface
)
186 prefixes
= Prefixes()
188 for iface
, selection
in sorted(self
.selections
.items()):
189 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
190 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
191 root
.appendChild(selection_elem
)
193 for name
, value
in selection
.attrs
.iteritems():
195 ns
, localName
= name
.split(' ', 1)
196 selection_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, value
)
197 elif name
== 'from-feed':
198 # Don't bother writing from-feed attr if it's the same as the interface
199 if value
!= selection
.attrs
['interface']:
200 selection_elem
.setAttributeNS(None, name
, value
)
201 elif name
not in ('main', 'self-test'): # (replaced by <command>)
202 selection_elem
.setAttributeNS(None, name
, value
)
204 if selection
.digests
:
205 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
206 for digest
in selection
.digests
:
207 aname
, avalue
= digest
.split('=', 1)
208 assert ':' not in aname
209 manifest_digest
.setAttribute(aname
, avalue
)
210 selection_elem
.appendChild(manifest_digest
)
212 for b
in selection
.bindings
:
213 selection_elem
.appendChild(b
._toxml
(doc
))
215 for dep
in selection
.dependencies
:
216 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
217 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
218 selection_elem
.appendChild(dep_elem
)
220 for m
in dep
.metadata
:
221 parts
= m
.split(' ', 1)
225 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
227 ns
, localName
= parts
228 dep_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, dep
.metadata
[m
])
230 for b
in dep
.bindings
:
231 dep_elem
.appendChild(b
._toxml
(doc
))
234 root
.appendChild(self
.command
._toxml
(doc
, prefixes
))
236 for ns
, prefix
in prefixes
.prefixes
.items():
237 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
242 return "Selections for " + self
.interface
244 def download_missing(self
, iface_cache
, fetcher
):
245 """Check all selected implementations are available.
246 Download any that are not present.
247 Note: package implementations (distribution packages) are ignored.
248 @param iface_cache: cache to find feeds with download information
249 @param fetcher: used to download missing implementations
250 @return: a L{tasks.Blocker} or None"""
251 from zeroinstall
.zerostore
import NotStored
253 # Check that every required selection is cached
254 needed_downloads
= []
255 for sel
in self
.selections
.values():
256 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
258 iface_cache
.stores
.lookup_any(sel
.digests
)
259 except NotStored
, ex
:
260 needed_downloads
.append(sel
)
261 if not needed_downloads
:
266 # We're missing some. For each one, get the feed it came from
267 # and find the corresponding <implementation> in that. This will
268 # tell us where to get it from.
269 # Note: we look for an implementation with the same ID. Maybe we
270 # should check it has the same digest(s) too?
272 for sel
in needed_downloads
:
273 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
274 feed
= iface_cache
.get_feed(feed_url
)
275 if feed
is None or sel
.id not in feed
.implementations
:
276 fetch_feed
= fetcher
.download_and_import_feed(feed_url
, iface_cache
)
278 tasks
.check(fetch_feed
)
280 feed
= iface_cache
.get_feed(feed_url
)
281 assert feed
, "Failed to get feed for %s" % feed_url
282 impl
= feed
.implementations
[sel
.id]
283 needed_impls
.append(impl
)
285 fetch_impls
= fetcher
.download_impls(needed_impls
, iface_cache
.stores
)
287 tasks
.check(fetch_impls
)
290 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
292 def __getitem__(self
, key
):
294 if isinstance(key
, basestring
):
295 return self
.selections
[key
]
296 sel
= self
.selections
[key
.uri
]
297 return sel
and sel
.impl
301 from zeroinstall
.injector
.iface_cache
import iface_cache
302 for (uri
, sel
) in self
.selections
.iteritems():
303 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
307 from zeroinstall
.injector
.iface_cache
import iface_cache
308 for (uri
, sel
) in self
.selections
.iteritems():
309 yield sel
and sel
.impl
313 from zeroinstall
.injector
.iface_cache
import iface_cache
314 for (uri
, sel
) in self
.selections
.iteritems():
315 yield iface_cache
.get_interface(uri
)
317 def get(self
, iface
, if_missing
):
319 sel
= self
.selections
.get(iface
.uri
, None)
327 s
.interface
= self
.interface
328 s
.selections
= self
.selections
.copy()
333 return list(self
.iteritems())