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 version: the implementation's version number
24 __slots__
= ['bindings', 'dependencies', 'attrs']
26 def __init__(self
, dependencies
, bindings
= None, attrs
= None):
27 if bindings
is None: bindings
= []
28 self
.dependencies
= dependencies
29 self
.bindings
= bindings
37 interface
= property(lambda self
: self
.attrs
['interface'])
38 id = property(lambda self
: self
.attrs
['id'])
39 version
= property(lambda self
: self
.attrs
['version'])
40 feed
= property(lambda self
: self
.attrs
.get('from-feed', self
.interface
))
41 main
= property(lambda self
: self
.attrs
.get('main', None))
46 class Selections(object):
48 A selected set of components which will make up a complete program.
49 @ivar interface: the interface of the program
51 @ivar selections: the selected implementations
52 @type selections: {str: L{Selection}}
54 __slots__
= ['interface', 'selections']
56 def __init__(self
, source
):
58 @param source: a map of implementations, policy or selections document
59 @type source: {str: L{Selection}} | L{Policy} | L{Element}
61 if isinstance(source
, dict):
62 self
.selections
= source
63 elif isinstance(source
, Policy
):
65 self
._init
_from
_policy
(source
)
66 elif isinstance(source
, Element
):
68 self
._init
_from
_qdom
(source
)
70 raise Exception(_("Source not a Policy or qdom.Element!"))
72 def _init_from_policy(self
, policy
):
73 """Set the selections from a policy.
74 @param policy: the policy giving the selected implementations."""
75 self
.interface
= policy
.root
77 for needed_iface
in policy
.implementation
:
78 impl
= policy
.implementation
[needed_iface
]
81 attrs
= impl
.metadata
.copy()
83 attrs
['version'] = impl
.get_version()
84 attrs
['interface'] = needed_iface
.uri
85 attrs
['from-feed'] = impl
.feed
.url
87 self
.selections
[needed_iface
.uri
] = Selection(impl
.requires
, impl
.bindings
, attrs
)
89 def _init_from_qdom(self
, root
):
90 """Parse and load a selections document.
91 @param root: a saved set of selections."""
92 self
.interface
= root
.getAttribute('interface')
95 for selection
in root
.childNodes
:
96 if selection
.uri
!= XMLNS_IFACE
:
98 if selection
.name
!= 'selection':
103 for dep_elem
in selection
.childNodes
:
104 if dep_elem
.uri
!= XMLNS_IFACE
:
106 if dep_elem
.name
in binding_names
:
107 bindings
.append(process_binding(dep_elem
))
108 elif dep_elem
.name
== 'requires':
109 dep
= process_depends(dep_elem
)
112 s
= Selection(requires
, bindings
, selection
.attrs
)
113 self
.selections
[selection
.attrs
['interface']] = s
116 """Create a DOM document for the selected implementations.
117 The document gives the URI of the root, plus each selected implementation.
118 For each selected implementation, we record the ID, the version, the URI and
119 (if different) the feed URL. We also record all the bindings needed.
120 @return: a new DOM Document"""
121 from xml
.dom
import minidom
, XMLNS_NAMESPACE
123 assert self
.interface
125 impl
= minidom
.getDOMImplementation()
127 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
129 root
= doc
.documentElement
130 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
132 root
.setAttributeNS(None, 'interface', self
.interface
)
134 def ensure_prefix(prefixes
, ns
):
135 prefix
= prefixes
.get(ns
, None)
138 prefix
= 'ns%d' % len(prefixes
)
139 prefixes
[ns
] = prefix
144 for iface
, selection
in sorted(self
.selections
.items()):
145 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
146 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
147 root
.appendChild(selection_elem
)
149 for name
, value
in selection
.attrs
.iteritems():
151 ns
, localName
= name
.split(' ', 1)
152 selection_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, value
)
153 elif name
!= 'from-feed':
154 selection_elem
.setAttributeNS(None, name
, value
)
155 elif value
!= selection
.attrs
['interface']:
156 # Don't bother writing from-feed attr if it's the same as the interface
157 selection_elem
.setAttributeNS(None, name
, value
)
159 for b
in selection
.bindings
:
160 selection_elem
.appendChild(b
._toxml
(doc
))
162 for dep
in selection
.dependencies
:
163 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
164 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
165 selection_elem
.appendChild(dep_elem
)
167 for m
in dep
.metadata
:
168 parts
= m
.split(' ', 1)
172 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
174 ns
, localName
= parts
175 dep_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, dep
.metadata
[m
])
177 for b
in dep
.bindings
:
178 dep_elem
.appendChild(b
._toxml
(doc
))
180 for ns
, prefix
in prefixes
.items():
181 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
186 return "Selections for " + self
.interface
188 def download_missing(self
, iface_cache
, fetcher
):
189 """Cache all selected implementations are available.
190 Download any that are not present.
191 @param iface_cache: cache to find feeds with download information
192 @param fetcher: used to download missing implementations
193 @return: a L{tasks.Blocker} or None"""
194 from zeroinstall
.zerostore
import NotStored
196 # Check that every required selection is cached
197 needed_downloads
= []
198 for sel
in self
.selections
.values():
200 if not iid
.startswith('/'):
202 iface_cache
.stores
.lookup(iid
)
203 except NotStored
, ex
:
204 needed_downloads
.append(sel
)
205 if not needed_downloads
:
210 # We're missing some. For each one, get the feed it came from
211 # and find the corresponding <implementation> in that. This will
212 # tell us where to get it from.
214 for sel
in needed_downloads
:
215 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
216 feed
= iface_cache
.get_feed(feed_url
)
217 if feed
is None or sel
.id not in feed
.implementations
:
218 yield fetcher
.download_and_import_feed(feed_url
, iface_cache
)
219 feed
= iface_cache
.get_feed(feed_url
)
220 assert feed
, "Failed to get feed for %s" % feed_url
221 impl
= feed
.implementations
[sel
.id]
222 needed_impls
.append(impl
)
224 yield fetcher
.download_impls(needed_impls
, iface_cache
.stores
)