Large-scale API cleanup
[zeroinstall/zeroinstall-afb.git] / zeroinstall / injector / reader.py
blob2a1ff0563588edb1e34329f281636632f9ffb44e
1 """
2 Parses an XML feed into a Python representation. You should probably use L{iface_cache.iface_cache} rather than the functions here.
3 """
5 # Copyright (C) 2009, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 from zeroinstall import _
9 import os
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 def update_from_cache(interface, iface_cache = None):
19 """Read a cached interface and any native feeds or user overrides.
20 @param interface: the interface object to update
21 @type interface: L{model.Interface}
22 @return: True if cached version and user overrides loaded OK.
23 False if upstream not cached. Local interfaces (starting with /) are
24 always considered to be cached, although they are not actually stored in the cache.
25 Internal: use L{iface_cache.IfaceCache.get_interface} instread.
26 @rtype: bool"""
27 interface.reset()
28 if iface_cache is None:
29 from zeroinstall.injector import policy
30 iface_cache = policy.get_deprecated_singleton_config().iface_cache
32 # Add the distribution package manager's version, if any
33 path = basedir.load_first_data(config_site, 'native_feeds', model._pretty_escape(interface.uri))
34 if path:
35 # Resolve any symlinks
36 info(_("Adding native packager feed '%s'"), path)
37 interface.extra_feeds.append(Feed(os.path.realpath(path), None, False))
39 update_user_overrides(interface)
41 main_feed = iface_cache.get_feed(interface.uri, force = True)
42 if main_feed:
43 update_user_feed_overrides(main_feed)
45 return main_feed is not None
47 def load_feed_from_cache(url, selections_ok = False):
48 """Load a feed. If the feed is remote, load from the cache. If local, load it directly.
49 @return: the feed, or None if it's remote and not cached."""
50 try:
51 if os.path.isabs(url):
52 debug(_("Loading local feed file '%s'"), url)
53 return load_feed(url, local = True, selections_ok = selections_ok)
54 else:
55 cached = basedir.load_first_cache(config_site, 'interfaces', escape(url))
56 if cached:
57 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url, 'cached': cached})
58 return load_feed(cached, local = False)
59 else:
60 return None
61 except InvalidInterface, ex:
62 ex.feed_url = url
63 raise
65 def update_user_feed_overrides(feed):
66 """Update a feed with user-supplied information.
67 Sets last_checked and user_stability ratings.
68 @param feed: feed to update
69 @since 0.49
70 """
71 user = basedir.load_first_config(config_site, config_prog,
72 'feeds', model._pretty_escape(feed.url))
73 if user is None:
74 # For files saved by 0launch < 0.49
75 user = basedir.load_first_config(config_site, config_prog,
76 'user_overrides', escape(feed.url))
77 if not user:
78 return
80 try:
81 root = qdom.parse(file(user))
82 except Exception, ex:
83 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
84 raise
86 last_checked = root.getAttribute('last-checked')
87 if last_checked:
88 feed.last_checked = int(last_checked)
90 for item in root.childNodes:
91 if item.uri != XMLNS_IFACE: continue
92 if item.name == 'implementation':
93 id = item.getAttribute('id')
94 assert id is not None
95 impl = feed.implementations.get(id, None)
96 if not impl:
97 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': feed})
98 continue
100 user_stability = item.getAttribute('user-stability')
101 if user_stability:
102 impl.user_stability = stability_levels[str(user_stability)]
104 def update_user_overrides(interface):
105 """Update an interface with user-supplied information.
106 Sets preferred stability and updates extra_feeds.
107 @param interface: the interface object to update
108 @type interface: L{model.Interface}
110 user = basedir.load_first_config(config_site, config_prog,
111 'interfaces', model._pretty_escape(interface.uri))
112 if user is None:
113 # For files saved by 0launch < 0.49
114 user = basedir.load_first_config(config_site, config_prog,
115 'user_overrides', escape(interface.uri))
116 if not user:
117 return
119 try:
120 root = qdom.parse(file(user))
121 except Exception, ex:
122 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
123 raise
125 stability_policy = root.getAttribute('stability-policy')
126 if stability_policy:
127 interface.set_stability_policy(stability_levels[str(stability_policy)])
129 for item in root.childNodes:
130 if item.uri != XMLNS_IFACE: continue
131 if item.name == 'feed':
132 feed_src = item.getAttribute('src')
133 if not feed_src:
134 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
135 interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs')))
137 def check_readable(feed_url, source):
138 """Test whether a feed file is valid.
139 @param feed_url: the feed's expected URL
140 @type feed_url: str
141 @param source: the name of the file to test
142 @type source: str
143 @return: the modification time in src (usually just the mtime of the file)
144 @rtype: int
145 @raise InvalidInterface: If the source's syntax is incorrect,
147 try:
148 feed = load_feed(source, local = False)
150 if feed.url != feed_url:
151 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
152 "%(feed_url)s is given in the feed, but\n"
153 "%(interface_uri)s was requested") %
154 {'feed_url': feed.url, 'interface_uri': feed_url})
155 return feed.last_modified
156 except InvalidInterface, ex:
157 info(_("Error loading feed:\n"
158 "Interface URI: %(uri)s\n"
159 "Local file: %(source)s\n"
160 "%(exception)s") %
161 {'uri': feed_url, 'source': source, 'exception': ex})
162 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': feed_url, 'exception': ex})
164 def update(interface, source, local = False, iface_cache = None):
165 """Read in information about an interface.
166 Deprecated.
167 @param interface: the interface object to update
168 @type interface: L{model.Interface}
169 @param source: the name of the file to read
170 @type source: str
171 @param local: use file's mtime for last-modified, and uri attribute is ignored
172 @raise InvalidInterface: if the source's syntax is incorrect
173 @return: the new feed (since 0.32)
174 @see: L{update_from_cache}, which calls this"""
175 assert isinstance(interface, Interface)
177 feed = load_feed(source, local)
179 if not local:
180 if feed.url != interface.uri:
181 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
182 "%(feed_url)s is given in the feed, but\n"
183 "%(interface_uri)s was requested") %
184 {'feed_url': feed.url, 'interface_uri': interface.uri})
186 if iface_cache is None:
187 from zeroinstall.injector import policy
188 iface_cache = policy.get_deprecated_singleton_config().iface_cache
189 iface_cache._feeds[unicode(interface.uri)] = feed
191 return feed
193 def load_feed(source, local = False, selections_ok = False):
194 """Load a feed from a local file.
195 @param source: the name of the file to read
196 @type source: str
197 @param local: this is a local feed
198 @type local: bool
199 @param selections_ok: if it turns out to be a local selections document, return that instead
200 @type selections_ok: bool
201 @raise InvalidInterface: if the source's syntax is incorrect
202 @return: the new feed
203 @since: 0.48
204 @see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
205 try:
206 root = qdom.parse(file(source))
207 except IOError, ex:
208 if ex.errno == 2:
209 raise InvalidInterface(_("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."), ex)
210 raise InvalidInterface(_("Can't read file"), ex)
211 except Exception, ex:
212 raise InvalidInterface(_("Invalid XML"), ex)
214 if local:
215 if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections':
216 from zeroinstall.injector import selections
217 return selections.Selections(root)
218 local_path = source
219 else:
220 local_path = None
221 feed = ZeroInstallFeed(root, local_path)
222 feed.last_modified = int(os.stat(source).st_mtime)
223 return feed