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
12 from zeroinstall
.support
import basedir
13 from zeroinstall
.injector
import qdom
14 from zeroinstall
.injector
.namespaces
import config_site
, config_prog
, XMLNS_IFACE
15 from zeroinstall
.injector
.model
import Interface
, InvalidInterface
, ZeroInstallFeed
, escape
, Feed
, stability_levels
16 from zeroinstall
.injector
import model
18 class MissingLocalFeed(InvalidInterface
):
21 def update_from_cache(interface
, iface_cache
= None):
22 """Read a cached interface and any native feeds or user overrides.
23 @param interface: the interface object to update
24 @type interface: L{model.Interface}
25 @return: True if cached version and user overrides loaded OK.
26 False if upstream not cached. Local interfaces (starting with /) are
27 always considered to be cached, although they are not actually stored in the cache.
28 Internal: use L{iface_cache.IfaceCache.get_interface} instread.
31 if iface_cache
is None:
32 from zeroinstall
.injector
import policy
33 iface_cache
= policy
.get_deprecated_singleton_config().iface_cache
35 # Add the distribution package manager's version, if any
36 path
= basedir
.load_first_data(config_site
, 'native_feeds', model
._pretty
_escape
(interface
.uri
))
38 # Resolve any symlinks
39 info(_("Adding native packager feed '%s'"), path
)
40 interface
.extra_feeds
.append(Feed(os
.path
.realpath(path
), None, False))
42 update_user_overrides(interface
)
44 main_feed
= iface_cache
.get_feed(interface
.uri
, force
= True)
46 update_user_feed_overrides(main_feed
)
48 return main_feed
is not None
50 def load_feed_from_cache(url
, selections_ok
= False):
51 """Load a feed. If the feed is remote, load from the cache. If local, load it directly.
52 @return: the feed, or None if it's remote and not cached."""
54 if os
.path
.isabs(url
):
55 debug(_("Loading local feed file '%s'"), url
)
56 return load_feed(url
, local
= True, selections_ok
= selections_ok
)
58 cached
= basedir
.load_first_cache(config_site
, 'interfaces', escape(url
))
60 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url
, 'cached': cached
})
61 return load_feed(cached
, local
= False)
64 except InvalidInterface
, ex
:
68 def update_user_feed_overrides(feed
):
69 """Update a feed with user-supplied information.
70 Sets last_checked and user_stability ratings.
71 @param feed: feed to update
74 user
= basedir
.load_first_config(config_site
, config_prog
,
75 'feeds', model
._pretty
_escape
(feed
.url
))
77 # For files saved by 0launch < 0.49
78 user
= basedir
.load_first_config(config_site
, config_prog
,
79 'user_overrides', escape(feed
.url
))
84 root
= qdom
.parse(file(user
))
86 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user
, 'exception': ex
})
89 last_checked
= root
.getAttribute('last-checked')
91 feed
.last_checked
= int(last_checked
)
93 for item
in root
.childNodes
:
94 if item
.uri
!= XMLNS_IFACE
: continue
95 if item
.name
== 'implementation':
96 id = item
.getAttribute('id')
98 impl
= feed
.implementations
.get(id, None)
100 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': feed
})
103 user_stability
= item
.getAttribute('user-stability')
105 impl
.user_stability
= stability_levels
[str(user_stability
)]
107 def update_user_overrides(interface
):
108 """Update an interface with user-supplied information.
109 Sets preferred stability and updates extra_feeds.
110 @param interface: the interface object to update
111 @type interface: L{model.Interface}
113 user
= basedir
.load_first_config(config_site
, config_prog
,
114 'interfaces', model
._pretty
_escape
(interface
.uri
))
116 # For files saved by 0launch < 0.49
117 user
= basedir
.load_first_config(config_site
, config_prog
,
118 'user_overrides', escape(interface
.uri
))
123 root
= qdom
.parse(file(user
))
124 except Exception, ex
:
125 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user
, 'exception': ex
})
128 stability_policy
= root
.getAttribute('stability-policy')
130 interface
.set_stability_policy(stability_levels
[str(stability_policy
)])
132 for item
in root
.childNodes
:
133 if item
.uri
!= XMLNS_IFACE
: continue
134 if item
.name
== 'feed':
135 feed_src
= item
.getAttribute('src')
137 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
138 interface
.extra_feeds
.append(Feed(feed_src
, item
.getAttribute('arch'), True, langs
= item
.getAttribute('langs')))
140 def check_readable(feed_url
, source
):
141 """Test whether a feed file is valid.
142 @param feed_url: the feed's expected URL
144 @param source: the name of the file to test
146 @return: the modification time in src (usually just the mtime of the file)
148 @raise InvalidInterface: If the source's syntax is incorrect,
151 feed
= load_feed(source
, local
= False)
153 if feed
.url
!= feed_url
:
154 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
155 "%(feed_url)s is given in the feed, but\n"
156 "%(interface_uri)s was requested") %
157 {'feed_url': feed
.url
, 'interface_uri': feed_url
})
158 return feed
.last_modified
159 except InvalidInterface
, ex
:
160 info(_("Error loading feed:\n"
161 "Interface URI: %(uri)s\n"
162 "Local file: %(source)s\n"
164 {'uri': feed_url
, 'source': source
, 'exception': ex
})
165 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': feed_url
, 'exception': ex
})
167 def update(interface
, source
, local
= False, iface_cache
= None):
168 """Read in information about an interface.
170 @param interface: the interface object to update
171 @type interface: L{model.Interface}
172 @param source: the name of the file to read
174 @param local: use file's mtime for last-modified, and uri attribute is ignored
175 @raise InvalidInterface: if the source's syntax is incorrect
176 @return: the new feed (since 0.32)
177 @see: L{update_from_cache}, which calls this"""
178 assert isinstance(interface
, Interface
)
180 feed
= load_feed(source
, local
)
183 if feed
.url
!= interface
.uri
:
184 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
185 "%(feed_url)s is given in the feed, but\n"
186 "%(interface_uri)s was requested") %
187 {'feed_url': feed
.url
, 'interface_uri': interface
.uri
})
189 if iface_cache
is None:
190 from zeroinstall
.injector
import policy
191 iface_cache
= policy
.get_deprecated_singleton_config().iface_cache
192 iface_cache
._feeds
[unicode(interface
.uri
)] = feed
196 def load_feed(source
, local
= False, selections_ok
= False, generate_sizes
= False, implementation_id_alg
=None):
197 """Load a feed from a local file.
198 @param source: the name of the file to read
200 @param local: this is a local feed
202 @param selections_ok: if it turns out to be a local selections document, return that instead
203 @type selections_ok: bool
204 @param generate_sizes: if True, sizes of archives with missing size attributes will be generated
205 @type generate_sizes: bool
206 @param implementation_id_alg: if specified, missing impl ids will be generated with this alg
207 @type implementation_id_alg: L{Algorithm}
208 @raise InvalidInterface: if the source's syntax is incorrect
209 @return: the new feed
211 @see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
213 root
= qdom
.parse(file(source
))
215 if ex
.errno
== 2 and local
:
216 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."))
217 raise InvalidInterface(_("Can't read file"), ex
)
218 except Exception, ex
:
219 raise InvalidInterface(_("Invalid XML"), ex
)
222 if selections_ok
and root
.uri
== XMLNS_IFACE
and root
.name
== 'selections':
223 from zeroinstall
.injector
import selections
224 return selections
.Selections(root
)
228 if implementation_id_alg
:
229 from zeroinstall
.injector
.config
import load_config
230 config
= load_config()
231 feed
= ZeroInstallFeed(root
, local_path
, None, generate_sizes
, implementation_id_alg
, config
.fetcher
, config
.stores
)
233 feed
= ZeroInstallFeed(root
, local_path
)
234 feed
.last_modified
= int(os
.stat(source
).st_mtime
)