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 def is_available(self
, stores
):
48 path
= self
.local_path
50 return os
.path
.exists(path
)
51 path
= stores
.lookup_maybe(self
.digests
)
52 return path
is not None
54 class ImplSelection(Selection
):
55 __slots__
= ['impl', 'dependencies', 'attrs']
57 def __init__(self
, iface_uri
, impl
, dependencies
):
60 self
.dependencies
= dependencies
62 attrs
= impl
.metadata
.copy()
64 attrs
['version'] = impl
.get_version()
65 attrs
['interface'] = iface_uri
66 attrs
['from-feed'] = impl
.feed
.url
68 attrs
['local-path'] = impl
.local_path
72 def bindings(self
): return self
.impl
.bindings
75 def digests(self
): return self
.impl
.digests
77 class XMLSelection(Selection
):
78 __slots__
= ['bindings', 'dependencies', 'attrs', 'digests']
80 def __init__(self
, dependencies
, bindings
= None, attrs
= None, digests
= None):
81 if bindings
is None: bindings
= []
82 if digests
is None: digests
= []
83 self
.dependencies
= dependencies
84 self
.bindings
= bindings
86 self
.digests
= digests
93 class Selections(object):
95 A selected set of components which will make up a complete program.
96 @ivar interface: the interface of the program
98 @ivar commands: how to run this selection (will contain more than one item if runners are used)
99 @type commands: [{L{Command}}]
100 @ivar selections: the selected implementations
101 @type selections: {str: L{Selection}}
103 __slots__
= ['interface', 'selections', 'commands']
105 def __init__(self
, source
):
107 @param source: a map of implementations, policy or selections document
108 @type source: {str: L{Selection}} | L{Policy} | L{Element}
114 # (Solver will fill everything in)
115 elif isinstance(source
, Policy
):
116 self
._init
_from
_policy
(source
)
117 elif isinstance(source
, Element
):
118 self
._init
_from
_qdom
(source
)
120 raise Exception(_("Source not a Policy or qdom.Element!"))
122 def _init_from_policy(self
, policy
):
123 """Set the selections from a policy.
124 @deprecated: use Solver.selections instead
125 @param policy: the policy giving the selected implementations."""
126 self
.interface
= policy
.root
127 self
.selections
= policy
.solver
.selections
.selections
128 self
.commands
= policy
.solver
.selections
.commands
130 def _init_from_qdom(self
, root
):
131 """Parse and load a selections document.
132 @param root: a saved set of selections."""
133 self
.interface
= root
.getAttribute('interface')
134 assert self
.interface
137 for selection
in root
.childNodes
:
138 if selection
.uri
!= XMLNS_IFACE
:
140 if selection
.name
!= 'selection':
141 if selection
.name
== 'command':
142 self
.commands
.append(Command(selection
, None))
148 for dep_elem
in selection
.childNodes
:
149 if dep_elem
.uri
!= XMLNS_IFACE
:
151 if dep_elem
.name
in binding_names
:
152 bindings
.append(process_binding(dep_elem
))
153 elif dep_elem
.name
== 'requires':
154 dep
= process_depends(dep_elem
, None)
156 elif dep_elem
.name
== 'manifest-digest':
157 for aname
, avalue
in dep_elem
.attrs
.iteritems():
158 digests
.append('%s=%s' % (aname
, avalue
))
160 # For backwards compatibility, allow getting the digest from the ID
161 sel_id
= selection
.attrs
['id']
162 local_path
= selection
.attrs
.get("local-path", None)
163 if (not digests
and not local_path
) and '=' in sel_id
:
164 alg
= sel_id
.split('=', 1)[0]
165 if alg
in ('sha1', 'sha1new', 'sha256'):
166 digests
.append(sel_id
)
168 iface_uri
= selection
.attrs
['interface']
170 s
= XMLSelection(requires
, bindings
, selection
.attrs
, digests
)
171 self
.selections
[iface_uri
] = s
173 if not self
.commands
:
174 # Old-style selections document; use the main attribute
175 if iface_uri
== self
.interface
:
176 root_sel
= self
.selections
[self
.interface
]
177 main
= root_sel
.attrs
.get('main', None)
179 self
.commands
= [Command(Element(XMLNS_IFACE
, 'command', {'path': main
}), None)]
182 """Create a DOM document for the selected implementations.
183 The document gives the URI of the root, plus each selected implementation.
184 For each selected implementation, we record the ID, the version, the URI and
185 (if different) the feed URL. We also record all the bindings needed.
186 @return: a new DOM Document"""
187 from xml
.dom
import minidom
, XMLNS_NAMESPACE
189 assert self
.interface
191 impl
= minidom
.getDOMImplementation()
193 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
195 root
= doc
.documentElement
196 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
198 root
.setAttributeNS(None, 'interface', self
.interface
)
200 prefixes
= Prefixes()
202 for iface
, selection
in sorted(self
.selections
.items()):
203 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
204 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
205 root
.appendChild(selection_elem
)
207 for name
, value
in selection
.attrs
.iteritems():
209 ns
, localName
= name
.split(' ', 1)
210 selection_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, value
)
211 elif name
== 'from-feed':
212 # Don't bother writing from-feed attr if it's the same as the interface
213 if value
!= selection
.attrs
['interface']:
214 selection_elem
.setAttributeNS(None, name
, value
)
215 elif name
not in ('main', 'self-test'): # (replaced by <command>)
216 selection_elem
.setAttributeNS(None, name
, value
)
218 if selection
.digests
:
219 manifest_digest
= doc
.createElementNS(XMLNS_IFACE
, 'manifest-digest')
220 for digest
in selection
.digests
:
221 aname
, avalue
= digest
.split('=', 1)
222 assert ':' not in aname
223 manifest_digest
.setAttribute(aname
, avalue
)
224 selection_elem
.appendChild(manifest_digest
)
226 for b
in selection
.bindings
:
227 selection_elem
.appendChild(b
._toxml
(doc
))
229 for dep
in selection
.dependencies
:
230 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
231 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
232 selection_elem
.appendChild(dep_elem
)
234 for m
in dep
.metadata
:
235 parts
= m
.split(' ', 1)
239 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
241 ns
, localName
= parts
242 dep_elem
.setAttributeNS(ns
, prefixes
.get(ns
) + ':' + localName
, dep
.metadata
[m
])
244 for b
in dep
.bindings
:
245 dep_elem
.appendChild(b
._toxml
(doc
))
247 for command
in self
.commands
:
248 root
.appendChild(command
._toxml
(doc
, prefixes
))
250 for ns
, prefix
in prefixes
.prefixes
.items():
251 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
256 return "Selections for " + self
.interface
258 def download_missing(self
, config
, _old
= None):
259 """Check all selected implementations are available.
260 Download any that are not present.
261 Note: package implementations (distribution packages) are ignored.
262 @param config: used to get iface_cache, stores and fetcher
263 @return: a L{tasks.Blocker} or None"""
264 from zeroinstall
.zerostore
import NotStored
267 config
= get_deprecated_singleton_config()
269 iface_cache
= config
.iface_cache
270 stores
= config
.stores
272 # Check that every required selection is cached
273 needed_downloads
= []
274 for sel
in self
.selections
.values():
275 if (not sel
.local_path
) and (not sel
.id.startswith('package:')):
277 stores
.lookup_any(sel
.digests
)
279 needed_downloads
.append(sel
)
280 if not needed_downloads
:
283 if config
.network_use
== model
.network_offline
:
284 from zeroinstall
import NeedDownload
285 raise NeedDownload(', '.join([str(x
) for x
in needed_downloads
]))
289 # We're missing some. For each one, get the feed it came from
290 # and find the corresponding <implementation> in that. This will
291 # tell us where to get it from.
292 # Note: we look for an implementation with the same ID. Maybe we
293 # should check it has the same digest(s) too?
295 for sel
in needed_downloads
:
296 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
297 feed
= iface_cache
.get_feed(feed_url
)
298 if feed
is None or sel
.id not in feed
.implementations
:
299 fetch_feed
= config
.fetcher
.download_and_import_feed(feed_url
, iface_cache
)
301 tasks
.check(fetch_feed
)
303 feed
= iface_cache
.get_feed(feed_url
)
304 assert feed
, "Failed to get feed for %s" % feed_url
305 impl
= feed
.implementations
[sel
.id]
306 needed_impls
.append(impl
)
308 fetch_impls
= config
.fetcher
.download_impls(needed_impls
, stores
)
310 tasks
.check(fetch_impls
)
313 # These (deprecated) methods are to make a Selections object look like the old Policy.implementation map...
315 def __getitem__(self
, key
):
317 if isinstance(key
, basestring
):
318 return self
.selections
[key
]
319 sel
= self
.selections
[key
.uri
]
320 return sel
and sel
.impl
324 iface_cache
= get_deprecated_singleton_config().iface_cache
325 for (uri
, sel
) in self
.selections
.iteritems():
326 yield (iface_cache
.get_interface(uri
), sel
and sel
.impl
)
330 for (uri
, sel
) in self
.selections
.iteritems():
331 yield sel
and sel
.impl
335 iface_cache
= get_deprecated_singleton_config().iface_cache
336 for (uri
, sel
) in self
.selections
.iteritems():
337 yield iface_cache
.get_interface(uri
)
339 def get(self
, iface
, if_missing
):
341 sel
= self
.selections
.get(iface
.uri
, None)
349 s
.interface
= self
.interface
350 s
.selections
= self
.selections
.copy()
355 return list(self
.iteritems())