2 Load and save a set of chosen implementations.
6 # Copyright (C) 2007, 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', 'self_rewrites', 'attrs']
25 def __init__(self
, dependencies
, bindings
, self_rewrites
, attrs
):
26 if bindings
is None: bindings
= []
27 self
.dependencies
= dependencies
28 self
.bindings
= bindings
29 self
.self_rewrites
= self_rewrites
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
= {'id': impl
.id,
82 'version': impl
.get_version(),
83 'interface': needed_iface
.uri
,
84 'from-feed': impl
.feed
.url
}
86 attrs
['main'] = impl
.main
88 self
.selections
[needed_iface
.uri
] = Selection(impl
.requires
, impl
.bindings
, impl
.self_rewrites
, attrs
)
90 def _init_from_qdom(self
, root
):
91 """Parse and load a selections document.
92 @param root: a saved set of selections."""
93 self
.interface
= root
.getAttribute('interface')
96 for selection
in root
.childNodes
:
97 if selection
.uri
!= XMLNS_IFACE
:
99 if selection
.name
!= 'selection':
104 for dep_elem
in selection
.childNodes
:
105 if dep_elem
.uri
!= XMLNS_IFACE
:
107 if dep_elem
.name
in binding_names
:
108 bindings
.append(process_binding(dep_elem
))
109 elif dep_elem
.name
== 'requires':
110 dep
= process_depends(dep_elem
)
113 # TODO: read self-rewrites
114 s
= Selection(requires
, bindings
, [], selection
.attrs
)
115 self
.selections
[selection
.attrs
['interface']] = s
118 """Create a DOM document for the selected implementations.
119 The document gives the URI of the root, plus each selected implementation.
120 For each selected implementation, we record the ID, the version, the URI and
121 (if different) the feed URL. We also record all the bindings needed.
122 @return: a new DOM Document"""
123 from xml
.dom
import minidom
, XMLNS_NAMESPACE
125 assert self
.interface
127 impl
= minidom
.getDOMImplementation()
129 doc
= impl
.createDocument(XMLNS_IFACE
, "selections", None)
131 root
= doc
.documentElement
132 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns', XMLNS_IFACE
)
134 root
.setAttributeNS(None, 'interface', self
.interface
)
136 def ensure_prefix(prefixes
, ns
):
137 prefix
= prefixes
.get(ns
, None)
140 prefix
= 'ns%d' % len(prefixes
)
141 prefixes
[ns
] = prefix
146 for iface
, selection
in sorted(self
.selections
.items()):
147 selection_elem
= doc
.createElementNS(XMLNS_IFACE
, 'selection')
148 selection_elem
.setAttributeNS(None, 'interface', selection
.interface
)
149 root
.appendChild(selection_elem
)
151 for name
, value
in selection
.attrs
.iteritems():
153 ns
, localName
= name
.split(' ', 1)
154 selection_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, value
)
155 elif name
!= 'from-feed':
156 selection_elem
.setAttributeNS(None, name
, value
)
157 elif value
!= selection
.attrs
['interface']:
158 # Don't bother writing from-feed attr if it's the same as the interface
159 selection_elem
.setAttributeNS(None, name
, value
)
161 for b
in selection
.bindings
:
162 selection_elem
.appendChild(b
._toxml
(doc
))
164 for dep
in selection
.dependencies
:
165 dep_elem
= doc
.createElementNS(XMLNS_IFACE
, 'requires')
166 dep_elem
.setAttributeNS(None, 'interface', dep
.interface
)
167 selection_elem
.appendChild(dep_elem
)
169 for m
in dep
.metadata
:
170 parts
= m
.split(' ', 1)
174 dep_elem
.setAttributeNS(None, localName
, dep
.metadata
[m
])
176 ns
, localName
= parts
177 dep_elem
.setAttributeNS(ns
, ensure_prefix(prefixes
, ns
) + ':' + localName
, dep
.metadata
[m
])
179 for b
in dep
.bindings
:
180 dep_elem
.appendChild(b
._toxml
(doc
))
182 for ns
, prefix
in prefixes
.items():
183 root
.setAttributeNS(XMLNS_NAMESPACE
, 'xmlns:' + prefix
, ns
)
188 return "Selections for " + self
.interface
190 def download_missing(self
, iface_cache
, fetcher
):
191 """Cache all selected implementations are available.
192 Download any that are not present.
193 @param iface_cache: cache to find feeds with download information
194 @param fetcher: used to download missing implementations
195 @return: a L{tasks.Blocker} or None"""
196 from zeroinstall
.zerostore
import NotStored
198 # Check that every required selection is cached
199 needed_downloads
= []
200 for sel
in self
.selections
.values():
202 if not iid
.startswith('/'):
204 iface_cache
.stores
.lookup(iid
)
205 except NotStored
, ex
:
206 needed_downloads
.append(sel
)
207 if not needed_downloads
:
212 # We're missing some. For each one, get the feed it came from
213 # and find the corresponding <implementation> in that. This will
214 # tell us where to get it from.
216 for sel
in needed_downloads
:
217 feed_url
= sel
.attrs
.get('from-feed', None) or sel
.attrs
['interface']
218 feed
= iface_cache
.get_feed(feed_url
)
219 if feed
is None or self
.id not in feed
.implementations
:
220 yield fetcher
.download_and_import_feed(feed_url
, iface_cache
)
221 feed
= iface_cache
.get_feed(feed_url
)
222 assert feed
, "Failed to get feed for %s" % feed_url
223 impl
= feed
.implementations
[sel
.id]
224 needed_impls
.append(impl
)
226 yield fetcher
.download_impls(needed_impls
, iface_cache
.stores
)