2 Parses an XML feed into a Python representation. You should probably use L{iface_cache.iface_cache} rather than the functions here.
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall
import _
10 from logging
import debug
, info
, warn
13 from zeroinstall
import support
14 from zeroinstall
.support
import basedir
15 from zeroinstall
.injector
import qdom
16 from zeroinstall
.injector
.namespaces
import config_site
, config_prog
, XMLNS_IFACE
17 from zeroinstall
.injector
.model
import Interface
, InvalidInterface
, ZeroInstallFeed
, escape
, Feed
, stability_levels
18 from zeroinstall
.injector
import model
20 class MissingLocalFeed(InvalidInterface
):
23 def _add_site_packages(interface
, site_packages
, known_site_feeds
):
24 for impl
in os
.listdir(site_packages
):
25 if impl
.startswith('.'): continue
26 feed
= os
.path
.join(site_packages
, impl
, '0install', 'feed.xml')
27 if not os
.path
.exists(feed
):
28 warn(_("Site-local feed {path} not found").format(path
= feed
))
29 debug("Adding site-local feed '%s'", feed
)
31 # (we treat these as user overrides in order to let old versions of 0install
33 interface
.extra_feeds
.append(Feed(feed
, None, user_override
= True, site_package
= True))
34 known_site_feeds
.add(feed
)
36 def update_from_cache(interface
, iface_cache
= None):
37 """Read a cached interface and any native feeds or user overrides.
38 @param interface: the interface object to update
39 @type interface: L{model.Interface}
40 @return: True if cached version and user overrides loaded OK.
41 False if upstream not cached. Local interfaces (starting with /) are
42 always considered to be cached, although they are not actually stored in the cache.
43 Internal: use L{iface_cache.IfaceCache.get_interface} instread.
46 if iface_cache
is None:
47 from zeroinstall
.injector
import policy
48 iface_cache
= policy
.get_deprecated_singleton_config().iface_cache
50 escaped_uri
= model
._pretty
_escape
(interface
.uri
)
51 # Add the distribution package manager's version, if any
52 path
= basedir
.load_first_data(config_site
, 'native_feeds', escaped_uri
)
54 # Resolve any symlinks
55 info(_("Adding native packager feed '%s'"), path
)
56 interface
.extra_feeds
.append(Feed(os
.path
.realpath(path
), None, False))
58 # Add locally-compiled binaries, if any
59 known_site_feeds
= set()
60 for path
in basedir
.load_data_paths(config_site
, 'site-packages', escaped_uri
):
62 _add_site_packages(interface
, path
, known_site_feeds
)
63 except Exception as ex
:
64 warn("Error loading site packages from {path}: {ex}".format(path
= path
, ex
= ex
))
66 update_user_overrides(interface
, known_site_feeds
)
68 main_feed
= iface_cache
.get_feed(interface
.uri
, force
= True)
70 update_user_feed_overrides(main_feed
)
72 return main_feed
is not None
74 def load_feed_from_cache(url
, selections_ok
= False):
75 """Load a feed. If the feed is remote, load from the cache. If local, load it directly.
76 @return: the feed, or None if it's remote and not cached."""
78 if os
.path
.isabs(url
):
79 debug(_("Loading local feed file '%s'"), url
)
80 return load_feed(url
, local
= True, selections_ok
= selections_ok
)
82 cached
= basedir
.load_first_cache(config_site
, 'interfaces', escape(url
))
84 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url
, 'cached': cached
})
85 return load_feed(cached
, local
= False)
88 except InvalidInterface
as ex
:
92 def update_user_feed_overrides(feed
):
93 """Update a feed with user-supplied information.
94 Sets last_checked and user_stability ratings.
95 @param feed: feed to update
98 user
= basedir
.load_first_config(config_site
, config_prog
,
99 'feeds', model
._pretty
_escape
(feed
.url
))
101 # For files saved by 0launch < 0.49
102 user
= basedir
.load_first_config(config_site
, config_prog
,
103 'user_overrides', escape(feed
.url
))
108 with
open(user
, 'rb') as stream
:
109 root
= qdom
.parse(stream
)
110 except Exception as ex
:
111 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user
, 'exception': ex
})
114 last_checked
= root
.getAttribute('last-checked')
116 feed
.last_checked
= int(last_checked
)
118 for item
in root
.childNodes
:
119 if item
.uri
!= XMLNS_IFACE
: continue
120 if item
.name
== 'implementation':
121 id = item
.getAttribute('id')
122 assert id is not None
123 impl
= feed
.implementations
.get(id, None)
125 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': feed
})
128 user_stability
= item
.getAttribute('user-stability')
130 impl
.user_stability
= stability_levels
[str(user_stability
)]
132 def update_user_overrides(interface
, known_site_feeds
= frozenset()):
133 """Update an interface with user-supplied information.
134 Sets preferred stability and updates extra_feeds.
135 @param interface: the interface object to update
136 @type interface: L{model.Interface}
137 @param known_site_feeds: feeds to ignore (for backwards compatibility)
139 user
= basedir
.load_first_config(config_site
, config_prog
,
140 'interfaces', model
._pretty
_escape
(interface
.uri
))
142 # For files saved by 0launch < 0.49
143 user
= basedir
.load_first_config(config_site
, config_prog
,
144 'user_overrides', escape(interface
.uri
))
149 with
open(user
, 'rb') as stream
:
150 root
= qdom
.parse(stream
)
151 except Exception as ex
:
152 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user
, 'exception': ex
})
155 stability_policy
= root
.getAttribute('stability-policy')
157 interface
.set_stability_policy(stability_levels
[str(stability_policy
)])
159 for item
in root
.childNodes
:
160 if item
.uri
!= XMLNS_IFACE
: continue
161 if item
.name
== 'feed':
162 feed_src
= item
.getAttribute('src')
164 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
165 if item
.getAttribute('site-package'):
166 # Site packages are detected earlier. This test isn't completely reliable,
167 # since older versions will remove the attribute when saving the config
168 # (hence the next test).
170 if feed_src
in known_site_feeds
:
172 interface
.extra_feeds
.append(Feed(feed_src
, item
.getAttribute('arch'), True, langs
= item
.getAttribute('langs')))
174 def check_readable(feed_url
, source
):
175 """Test whether a feed file is valid.
176 @param feed_url: the feed's expected URL
178 @param source: the name of the file to test
180 @return: the modification time in src (usually just the mtime of the file)
182 @raise InvalidInterface: If the source's syntax is incorrect,
185 feed
= load_feed(source
, local
= False)
187 if feed
.url
!= feed_url
:
188 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
189 "%(feed_url)s is given in the feed, but\n"
190 "%(interface_uri)s was requested") %
191 {'feed_url': feed
.url
, 'interface_uri': feed_url
})
192 return feed
.last_modified
193 except InvalidInterface
as ex
:
194 info(_("Error loading feed:\n"
195 "Interface URI: %(uri)s\n"
196 "Local file: %(source)s\n"
198 {'uri': feed_url
, 'source': source
, 'exception': ex
})
199 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': feed_url
, 'exception': ex
})
201 def update(interface
, source
, local
= False, iface_cache
= None):
202 """Read in information about an interface.
204 @param interface: the interface object to update
205 @type interface: L{model.Interface}
206 @param source: the name of the file to read
208 @param local: use file's mtime for last-modified, and uri attribute is ignored
209 @raise InvalidInterface: if the source's syntax is incorrect
210 @return: the new feed (since 0.32)
211 @see: L{update_from_cache}, which calls this"""
212 assert isinstance(interface
, Interface
)
214 feed
= load_feed(source
, local
)
217 if feed
.url
!= interface
.uri
:
218 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
219 "%(feed_url)s is given in the feed, but\n"
220 "%(interface_uri)s was requested") %
221 {'feed_url': feed
.url
, 'interface_uri': interface
.uri
})
223 if iface_cache
is None:
224 from zeroinstall
.injector
import policy
225 iface_cache
= policy
.get_deprecated_singleton_config().iface_cache
226 iface_cache
._feeds
[support
.unicode(interface
.uri
)] = feed
230 def load_feed(source
, local
= False, selections_ok
= False):
231 """Load a feed from a local file.
232 @param source: the name of the file to read
234 @param local: this is a local feed
236 @param selections_ok: if it turns out to be a local selections document, return that instead
237 @type selections_ok: bool
238 @raise InvalidInterface: if the source's syntax is incorrect
239 @return: the new feed
241 @see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
243 with
open(source
, 'rb') as stream
:
244 root
= qdom
.parse(stream
)
245 except IOError as ex
:
246 if ex
.errno
== errno
.ENOENT
and local
:
247 raise MissingLocalFeed(_("Feed not found. Perhaps this is a local feed that no longer exists? You can remove it from the list of feeds in that case."))
248 raise InvalidInterface(_("Can't read file"), ex
)
249 except Exception as ex
:
250 raise InvalidInterface(_("Invalid XML"), ex
)
253 if selections_ok
and root
.uri
== XMLNS_IFACE
and root
.name
== 'selections':
254 from zeroinstall
.injector
import selections
255 return selections
.Selections(root
)
259 feed
= ZeroInstallFeed(root
, local_path
)
260 feed
.last_modified
= int(os
.stat(source
).st_mtime
)