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
.injector
.policy
import Policy
10 from zeroinstall
.injector
.model
import process_binding
, process_depends
, binding_names
11 from zeroinstall
.injector
.namespaces
import XMLNS_IFACE
12 from zeroinstall
.injector
.qdom
import Element
13 from zeroinstall
.support
import tasks
15 class Selection(object):
16 """A single selected implementation in a L{Selections} set.
17 @ivar dependencies: list of dependencies
18 @type dependencies: [L{model.Dependency}]
19 @ivar attrs: XML attributes map (name is in the format "{namespace} {localName}")
20 @type attrs: {str: str}
21 @ivar version: the implementation's version number
23 __slots__
= ['bindings', 'dependencies', 'attrs']
25 def __init__(self
, dependencies
, bindings
= None, attrs
= None):
26 if bindings
is None: bindings
= []
27 self
.dependencies
= dependencies
28 self
.bindings
= bindings
36 interface
= property(lambda self
: self
.attrs
['interface'])
37 id = property(lambda self
: self
.attrs
['id'])
38 version
= property(lambda self
: self
.attrs
['version'])
39 feed
= property(lambda self
: self
.attrs
.get('from-feed', self
.interface
))
40 main
= property(lambda self
: self
.attrs
.get('main', None))
45 class Selections(object):
47 A selected set of components which will make up a complete program.
48 @ivar interface: the interface of the program
50 @ivar selections: the selected implementations
51 @type selections: {str: L{Selection}}
53 __slots__
= ['interface', 'selections']
55 def __init__(self
, source
):
57 @param source: a map of implementations, policy or selections document
58 @type source: {str: L{Selection}} | L{Policy} | L{Element}
60 if isinstance(source
, dict):
61 self
.selections
= source
62 elif isinstance(source
, Policy
):
64 self
._init
_from
_policy
(source
)
65 elif isinstance(source
, Element
):
67 self
._init
_from
_qdom
(source
)
69 raise Exception("Source not a Policy or qdom.Element!")
71 def _init_from_policy(self
, policy
):
72 """Set the selections from a policy.
73 @param policy: the policy giving the selected implementations."""
74 self
.interface
= policy
.root
76 for needed_iface
in policy
.implementation
:
77 impl
= policy
.implementation
[needed_iface
]
80 attrs
= impl
.metadata
.copy()
82 attrs
['version'] = impl
.get_version()
83 attrs
['interface'] = needed_iface
.uri
84 attrs
['from-feed'] = impl
.feed
.url
86 self
.selections
[needed_iface
.uri
] = Selection(impl
.requires
, impl
.bindings
, attrs
)
88 def _init_from_qdom(self
, root
):
89 """Parse and load a selections document.
90 @param root: a saved set of selections."""
91 self
.interface
= root
.getAttribute('interface')
94 for selection
in root
.childNodes
:
95 if selection
.uri
!= XMLNS_IFACE
:
97 if selection
.name
!= 'selection':
102 for dep_elem
in selection
.childNodes
:
103 if dep_elem
.uri
!= XMLNS_IFACE
:
105 if dep_elem
.name
in binding_names
:
106 bindings
.append(process_binding(dep_elem
))
107 elif dep_elem
.name
== 'requires':
108 dep
= process_depends(dep_elem
)
111 s
= Selection(requires
, bindings
, selection
.attrs
)
112 self
.selections
[selection
.attrs
['interface']] = s
115 """Create a DOM document for the selected implementations.
116 The document gives the URI of the root, plus each selected implementation.
117 For each selected implementation, we record the ID, the version, the URI and
118 (if different) the feed URL. We also record all the bindings needed.
119 @return: a new DOM Document"""
120 from xml
.dom
import minidom
, XMLNS_NAMESPACE
122 assert self
.interface
124 impl
= minidom
.getDOMImplementation()
126 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
128 root
= doc
.documentElement
129 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
131 root
.setAttributeNS(None, 'interface', self
.interface
)
133 def ensure_prefix(prefixes
, ns
):
134 prefix
= prefixes
.get(ns
, None)
137 prefix
= 'ns%d' % len(prefixes
)
138 prefixes
[ns
] = prefix
143 for iface
, selection
in sorted(self
.selections
.items()):
144 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
145 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
146 root
.appendChild(selection_elem
)
148 for name
, value
in selection
.attrs
.iteritems():
150 ns
, localName
= name
.split(' ', 1)
151 selection_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, value
)
152 elif name
!= 'from-feed':
153 selection_elem
.setAttributeNS(None, name
, value
)
154 elif value
!= selection
.attrs
['interface']:
155 # Don't bother writing from-feed attr if it's the same as the interface
156 selection_elem
.setAttributeNS(None, name
, value
)
158 for b
in selection
.bindings
:
159 selection_elem
.appendChild(b
._toxml
(doc
))
161 for dep
in selection
.dependencies
:
162 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
163 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
164 selection_elem
.appendChild(dep_elem
)
166 for m
in dep
.metadata
:
167 parts
= m
.split(' ', 1)
171 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
173 ns
, localName
= parts
174 dep_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, dep
.metadata
[m
])
176 for b
in dep
.bindings
:
177 dep_elem
.appendChild(b
._toxml
(doc
))
179 for ns
, prefix
in prefixes
.items():
180 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
185 return "Selections for " + self
.interface
187 def download_missing(self
, iface_cache
, fetcher
):
188 """Cache all selected implementations are available.
189 Download any that are not present.
190 @param iface_cache: cache to find feeds with download information
191 @param fetcher: used to download missing implementations
192 @return: a L{tasks.Blocker} or None"""
193 from zeroinstall
.zerostore
import NotStored
195 # Check that every required selection is cached
196 needed_downloads
= []
197 for sel
in self
.selections
.values():
199 if not iid
.startswith('/'):
201 iface_cache
.stores
.lookup(iid
)
202 except NotStored
, ex
:
203 needed_downloads
.append(sel
)
204 if not needed_downloads
:
209 # We're missing some. For each one, get the feed it came from
210 # and find the corresponding <implementation> in that. This will
211 # tell us where to get it from.
213 for sel
in needed_downloads
:
214 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
215 feed
= iface_cache
.get_feed(feed_url
)
216 if feed
is None or sel
.id not in feed
.implementations
:
217 yield fetcher
.download_and_import_feed(feed_url
, iface_cache
)
218 feed
= iface_cache
.get_feed(feed_url
)
219 assert feed
, "Failed to get feed for %s" % feed_url
220 impl
= feed
.implementations
[sel
.id]
221 needed_impls
.append(impl
)
223 yield fetcher
.download_impls(needed_impls
, iface_cache
.stores
)