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
.support
import basedir
14 from zeroinstall
.injector
import qdom
15 from zeroinstall
.injector
.namespaces
import config_site
, config_prog
, XMLNS_IFACE
16 from zeroinstall
.injector
.model
import Interface
, InvalidInterface
, ZeroInstallFeed
, escape
, Feed
, stability_levels
17 from zeroinstall
.injector
import model
19 class MissingLocalFeed(InvalidInterface
):
22 def _add_site_packages(interface
, site_packages
, known_site_feeds
):
23 for impl
in os
.listdir(site_packages
):
24 if impl
.startswith('.'): continue
25 feed
= os
.path
.join(site_packages
, impl
, '0install', 'feed.xml')
26 if not os
.path
.exists(feed
):
27 warn(_("Site-local feed {path} not found").format(path
= feed
))
28 debug("Adding site-local feed '%s'", feed
)
30 # (we treat these as user overrides in order to let old versions of 0install
32 interface
.extra_feeds
.append(Feed(feed
, None, user_override
= True, site_package
= True))
33 known_site_feeds
.add(feed
)
35 def update_from_cache(interface
, iface_cache
= None):
36 """Read a cached interface and any native feeds or user overrides.
37 @param interface: the interface object to update
38 @type interface: L{model.Interface}
39 @return: True if cached version and user overrides loaded OK.
40 False if upstream not cached. Local interfaces (starting with /) are
41 always considered to be cached, although they are not actually stored in the cache.
42 Internal: use L{iface_cache.IfaceCache.get_interface} instread.
45 if iface_cache
is None:
46 from zeroinstall
.injector
import policy
47 iface_cache
= policy
.get_deprecated_singleton_config().iface_cache
49 escaped_uri
= model
._pretty
_escape
(interface
.uri
)
50 # Add the distribution package manager's version, if any
51 path
= basedir
.load_first_data(config_site
, 'native_feeds', escaped_uri
)
53 # Resolve any symlinks
54 info(_("Adding native packager feed '%s'"), path
)
55 interface
.extra_feeds
.append(Feed(os
.path
.realpath(path
), None, False))
57 # Add locally-compiled binaries, if any
58 known_site_feeds
= set()
59 for path
in basedir
.load_data_paths(config_site
, 'site-packages', escaped_uri
):
61 _add_site_packages(interface
, path
, known_site_feeds
)
62 except Exception as ex
:
63 warn("Error loading site packages from {path}: {ex}".format(path
= path
, ex
= ex
))
65 update_user_overrides(interface
, known_site_feeds
)
67 main_feed
= iface_cache
.get_feed(interface
.uri
, force
= True)
69 update_user_feed_overrides(main_feed
)
71 return main_feed
is not None
73 def load_feed_from_cache(url
, selections_ok
= False):
74 """Load a feed. If the feed is remote, load from the cache. If local, load it directly.
75 @return: the feed, or None if it's remote and not cached."""
77 if os
.path
.isabs(url
):
78 debug(_("Loading local feed file '%s'"), url
)
79 return load_feed(url
, local
= True, selections_ok
= selections_ok
)
81 cached
= basedir
.load_first_cache(config_site
, 'interfaces', escape(url
))
83 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url
, 'cached': cached
})
84 return load_feed(cached
, local
= False)
87 except InvalidInterface
as ex
:
91 def update_user_feed_overrides(feed
):
92 """Update a feed with user-supplied information.
93 Sets last_checked and user_stability ratings.
94 @param feed: feed to update
97 user
= basedir
.load_first_config(config_site
, config_prog
,
98 'feeds', model
._pretty
_escape
(feed
.url
))
100 # For files saved by 0launch < 0.49
101 user
= basedir
.load_first_config(config_site
, config_prog
,
102 'user_overrides', escape(feed
.url
))
107 with
open(user
, 'rb') as stream
:
108 root
= qdom
.parse(stream
)
109 except Exception as ex
:
110 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user
, 'exception': ex
})
113 last_checked
= root
.getAttribute('last-checked')
115 feed
.last_checked
= int(last_checked
)
117 for item
in root
.childNodes
:
118 if item
.uri
!= XMLNS_IFACE
: continue
119 if item
.name
== 'implementation':
120 id = item
.getAttribute('id')
121 assert id is not None
122 impl
= feed
.implementations
.get(id, None)
124 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': feed
})
127 user_stability
= item
.getAttribute('user-stability')
129 impl
.user_stability
= stability_levels
[str(user_stability
)]
131 def update_user_overrides(interface
, known_site_feeds
= frozenset()):
132 """Update an interface with user-supplied information.
133 Sets preferred stability and updates extra_feeds.
134 @param interface: the interface object to update
135 @type interface: L{model.Interface}
136 @param known_site_feeds: feeds to ignore (for backwards compatibility)
138 user
= basedir
.load_first_config(config_site
, config_prog
,
139 'interfaces', model
._pretty
_escape
(interface
.uri
))
141 # For files saved by 0launch < 0.49
142 user
= basedir
.load_first_config(config_site
, config_prog
,
143 'user_overrides', escape(interface
.uri
))
148 with
open(user
, 'rb') as stream
:
149 root
= qdom
.parse(stream
)
150 except Exception as ex
:
151 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user
, 'exception': ex
})
154 stability_policy
= root
.getAttribute('stability-policy')
156 interface
.set_stability_policy(stability_levels
[str(stability_policy
)])
158 for item
in root
.childNodes
:
159 if item
.uri
!= XMLNS_IFACE
: continue
160 if item
.name
== 'feed':
161 feed_src
= item
.getAttribute('src')
163 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
164 if item
.getAttribute('site-package'):
165 # Site packages are detected earlier. This test isn't completely reliable,
166 # since older versions will remove the attribute when saving the config
167 # (hence the next test).
169 if feed_src
in known_site_feeds
:
171 interface
.extra_feeds
.append(Feed(feed_src
, item
.getAttribute('arch'), True, langs
= item
.getAttribute('langs')))
173 def check_readable(feed_url
, source
):
174 """Test whether a feed file is valid.
175 @param feed_url: the feed's expected URL
177 @param source: the name of the file to test
179 @return: the modification time in src (usually just the mtime of the file)
181 @raise InvalidInterface: If the source's syntax is incorrect,
184 feed
= load_feed(source
, local
= False)
186 if feed
.url
!= feed_url
:
187 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
188 "%(feed_url)s is given in the feed, but\n"
189 "%(interface_uri)s was requested") %
190 {'feed_url': feed
.url
, 'interface_uri': feed_url
})
191 return feed
.last_modified
192 except InvalidInterface
as ex
:
193 info(_("Error loading feed:\n"
194 "Interface URI: %(uri)s\n"
195 "Local file: %(source)s\n"
197 {'uri': feed_url
, 'source': source
, 'exception': ex
})
198 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': feed_url
, 'exception': ex
})
200 def update(interface
, source
, local
= False, iface_cache
= None):
201 """Read in information about an interface.
203 @param interface: the interface object to update
204 @type interface: L{model.Interface}
205 @param source: the name of the file to read
207 @param local: use file's mtime for last-modified, and uri attribute is ignored
208 @raise InvalidInterface: if the source's syntax is incorrect
209 @return: the new feed (since 0.32)
210 @see: L{update_from_cache}, which calls this"""
211 assert isinstance(interface
, Interface
)
213 feed
= load_feed(source
, local
)
216 if feed
.url
!= interface
.uri
:
217 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
218 "%(feed_url)s is given in the feed, but\n"
219 "%(interface_uri)s was requested") %
220 {'feed_url': feed
.url
, 'interface_uri': interface
.uri
})
222 if iface_cache
is None:
223 from zeroinstall
.injector
import policy
224 iface_cache
= policy
.get_deprecated_singleton_config().iface_cache
225 iface_cache
._feeds
[model
.unicode(interface
.uri
)] = feed
229 def load_feed(source
, local
= False, selections_ok
= False):
230 """Load a feed from a local file.
231 @param source: the name of the file to read
233 @param local: this is a local feed
235 @param selections_ok: if it turns out to be a local selections document, return that instead
236 @type selections_ok: bool
237 @raise InvalidInterface: if the source's syntax is incorrect
238 @return: the new feed
240 @see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
242 with
open(source
, 'rb') as stream
:
243 root
= qdom
.parse(stream
)
244 except IOError as ex
:
245 if ex
.errno
== errno
.ENOENT
and local
:
246 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."))
247 raise InvalidInterface(_("Can't read file"), ex
)
248 except Exception as ex
:
249 raise InvalidInterface(_("Invalid XML"), ex
)
252 if selections_ok
and root
.uri
== XMLNS_IFACE
and root
.name
== 'selections':
253 from zeroinstall
.injector
import selections
254 return selections
.Selections(root
)
258 feed
= ZeroInstallFeed(root
, local_path
)
259 feed
.last_modified
= int(os
.stat(source
).st_mtime
)