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
import model
11 from zeroinstall
.injector
.policy
import Policy
, get_deprecated_singleton_config
12 from zeroinstall
.injector
.model
import process_binding
, process_depends
, binding_names
, Command
13 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
14 from zeroinstall
.injector
.qdom
import Element
, Prefixes
15 from zeroinstall
.support
import tasks
17 class Selection(object):
18 """A single selected implementation in a L{Selections} set.
19 @ivar dependencies: list of dependencies
20 @type dependencies: [L{model.Dependency}]
21 @ivar attrs: XML attributes map (name is in the format "{namespace} {localName}")
22 @type attrs: {str: str}
23 @ivar digests: a list of manifest digests
25 @ivar version: the implementation's version number
28 interface
= property(lambda self
: self
.attrs
['interface'])
29 id = property(lambda self
: self
.attrs
['id'])
30 version
= property(lambda self
: self
.attrs
['version'])
31 feed
= property(lambda self
: self
.attrs
.get('from-feed', self
.interface
))
32 main
= property(lambda self
: self
.attrs
.get('main', None))
36 local_path
= self
.attrs
.get('local-path', None)
39 if self
.id.startswith('/'):
46 class ImplSelection(Selection
):
47 __slots__
= ['impl', 'dependencies', 'attrs']
49 def __init__(self
, iface_uri
, impl
, dependencies
):
52 self
.dependencies
= dependencies
54 attrs
= impl
.metadata
.copy()
56 attrs
['version'] = impl
.get_version()
57 attrs
['interface'] = iface_uri
58 attrs
['from-feed'] = impl
.feed
.url
60 attrs
['local-path'] = impl
.local_path
64 def bindings(self
): return self
.impl
.bindings
67 def digests(self
): return self
.impl
.digests
69 class XMLSelection(Selection
):
70 __slots__
= ['bindings', 'dependencies', 'attrs', 'digests']
72 def __init__(self
, dependencies
, bindings
= None, attrs
= None, digests
= None):
73 if bindings
is None: bindings
= []
74 if digests
is None: digests
= []
75 self
.dependencies
= dependencies
76 self
.bindings
= bindings
78 self
.digests
= digests
85 class Selections(object):
87 A selected set of components which will make up a complete program.
88 @ivar interface: the interface of the program
90 @ivar commands: how to run this selection (will contain more than one item if runners are used)
91 @type commands: [{L{Command}}]
92 @ivar selections: the selected implementations
93 @type selections: {str: L{Selection}}
95 __slots__
= ['interface', 'selections', 'commands']
97 def __init__(self
, source
):
99 @param source: a map of implementations, policy or selections document
100 @type source: {str: L{Selection}} | L{Policy} | L{Element}
106 # (Solver will fill everything in)
107 elif isinstance(source
, Policy
):
108 self
._init
_from
_policy
(source
)
109 elif isinstance(source
, Element
):
110 self
._init
_from
_qdom
(source
)
112 raise Exception(_("Source not a Policy or qdom.Element!"))
114 def _init_from_policy(self
, policy
):
115 """Set the selections from a policy.
116 @deprecated: use Solver.selections instead
117 @param policy: the policy giving the selected implementations."""
118 self
.interface
= policy
.root
119 self
.selections
= policy
.solver
.selections
.selections
120 self
.commands
= policy
.solver
.selections
.commands
122 def _init_from_qdom(self
, root
):
123 """Parse and load a selections document.
124 @param root: a saved set of selections."""
125 self
.interface
= root
.getAttribute('interface')
126 assert self
.interface
129 for selection
in root
.childNodes
:
130 if selection
.uri
!= XMLNS_IFACE
:
132 if selection
.name
!= 'selection':
133 if selection
.name
== 'command':
134 self
.commands
.append(Command(selection
, None))
140 for dep_elem
in selection
.childNodes
:
141 if dep_elem
.uri
!= XMLNS_IFACE
:
143 if dep_elem
.name
in binding_names
:
144 bindings
.append(process_binding(dep_elem
))
145 elif dep_elem
.name
== 'requires':
146 dep
= process_depends(dep_elem
, None)
148 elif dep_elem
.name
== 'manifest-digest':
149 for aname
, avalue
in dep_elem
.attrs
.iteritems():
150 digests
.append('%s=%s' % (aname
, avalue
))
152 # For backwards compatibility, allow getting the digest from the ID
153 sel_id
= selection
.attrs
['id']
154 local_path
= selection
.attrs
.get("local-path", None)
155 if (not digests
and not local_path
) and '=' in sel_id
:
156 alg
= sel_id
.split('=', 1)[0]
157 if alg
in ('sha1', 'sha1new', 'sha256'):
158 digests
.append(sel_id
)
160 iface_uri
= selection
.attrs
['interface']
162 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
)
163 self
.selections
[iface_uri
] = s
165 if not self
.commands
:
166 # Old-style selections document; use the main attribute
167 if iface_uri
== self
.interface
:
168 root_sel
= self
.selections
[self
.interface
]
169 main
= root_sel
.attrs
.get('main', None)
171 self
.commands
= [Command(Element(XMLNS_IFACE
, 'command', {'path': main
}), None)]
174 """Create a DOM document for the selected implementations.
175 The document gives the URI of the root, plus each selected implementation.
176 For each selected implementation, we record the ID, the version, the URI and
177 (if different) the feed URL. We also record all the bindings needed.
178 @return: a new DOM Document"""
179 from xml
.dom
import minidom
, XMLNS_NAMESPACE
181 assert self
.interface
183 impl
= minidom
.getDOMImplementation()
185 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
187 root
= doc
.documentElement
188 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
190 root
.setAttributeNS(None, 'interface', self
.interface
)
192 prefixes
= Prefixes()
194 for iface
, selection
in sorted(self
.selections
.items()):
195 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
196 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
197 root
.appendChild(selection_elem
)
199 for name
, value
in selection
.attrs
.iteritems():
201 ns
, localName
= name
.split(' ', 1)
202 selection_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, value
)
203 elif name
== 'from-feed':
204 # Don't bother writing from-feed attr if it's the same as the interface
205 if value
!= selection
.attrs
['interface']:
206 selection_elem
.setAttributeNS(None, name
, value
)
207 elif name
not in ('main', 'self-test'): # (replaced by <command>)
208 selection_elem
.setAttributeNS(None, name
, value
)
210 if selection
.digests
:
211 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
212 for digest
in selection
.digests
:
213 aname
, avalue
= digest
.split('=', 1)
214 assert ':' not in aname
215 manifest_digest
.setAttribute(aname
, avalue
)
216 selection_elem
.appendChild(manifest_digest
)
218 for b
in selection
.bindings
:
219 selection_elem
.appendChild(b
._toxml
(doc
))
221 for dep
in selection
.dependencies
:
222 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
223 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
224 selection_elem
.appendChild(dep_elem
)
226 for m
in dep
.metadata
:
227 parts
= m
.split(' ', 1)
231 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
233 ns
, localName
= parts
234 dep_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, dep
.metadata
[m
])
236 for b
in dep
.bindings
:
237 dep_elem
.appendChild(b
._toxml
(doc
))
239 for command
in self
.commands
:
240 root
.appendChild(command
._toxml
(doc
, prefixes
))
242 for ns
, prefix
in prefixes
.prefixes
.items():
243 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
248 return "Selections for " + self
.interface
250 def download_missing(self
, config
, _old
= None):
251 """Check all selected implementations are available.
252 Download any that are not present.
253 Note: package implementations (distribution packages) are ignored.
254 @param config: used to get iface_cache, stores and fetcher
255 @return: a L{tasks.Blocker} or None"""
256 from zeroinstall
.zerostore
import NotStored
259 config
= get_deprecated_singleton_config()
261 iface_cache
= config
.iface_cache
262 stores
= config
.stores
264 # Check that every required selection is cached
265 needed_downloads
= []
266 for sel
in self
.selections
.values():
267 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
269 stores
.lookup_any(sel
.digests
)
271 needed_downloads
.append(sel
)
272 if not needed_downloads
:
275 if config
.network_use
== model
.network_offline
:
276 from zeroinstall
import NeedDownload
277 raise NeedDownload(', '.join([str(x
) for x
in needed_downloads
]))
281 # We're missing some. For each one, get the feed it came from
282 # and find the corresponding <implementation> in that. This will
283 # tell us where to get it from.
284 # Note: we look for an implementation with the same ID. Maybe we
285 # should check it has the same digest(s) too?
287 for sel
in needed_downloads
:
288 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
289 feed
= iface_cache
.get_feed(feed_url
)
290 if feed
is None or sel
.id not in feed
.implementations
:
291 fetch_feed
= config
.fetcher
.download_and_import_feed(feed_url
, iface_cache
)
293 tasks
.check(fetch_feed
)
295 feed
= iface_cache
.get_feed(feed_url
)
296 assert feed
, "Failed to get feed for %s" % feed_url
297 impl
= feed
.implementations
[sel
.id]
298 needed_impls
.append(impl
)
300 fetch_impls
= config
.fetcher
.download_impls(needed_impls
, stores
)
302 tasks
.check(fetch_impls
)
305 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
307 def __getitem__(self
, key
):
309 if isinstance(key
, basestring
):
310 return self
.selections
[key
]
311 sel
= self
.selections
[key
.uri
]
312 return sel
and sel
.impl
316 iface_cache
= get_deprecated_singleton_config().iface_cache
317 for (uri
, sel
) in self
.selections
.iteritems():
318 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
322 for (uri
, sel
) in self
.selections
.iteritems():
323 yield sel
and sel
.impl
327 iface_cache
= get_deprecated_singleton_config().iface_cache
328 for (uri
, sel
) in self
.selections
.iteritems():
329 yield iface_cache
.get_interface(uri
)
331 def get(self
, iface
, if_missing
):
333 sel
= self
.selections
.get(iface
.uri
, None)
341 s
.interface
= self
.interface
342 s
.selections
= self
.selections
.copy()
347 return list(self
.iteritems())