Refactored code as required for the new 0publish
[zeroinstall/zeroinstall-limyreth.git] / zeroinstall / injector / reader.py
blobdef987de158d61ee8eca499850d72a43c8a40491
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 class MissingLocalFeed(InvalidInterface):
19 pass
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.
29 @rtype: bool"""
30 interface.reset()
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))
37 if path:
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)
45 if main_feed:
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."""
53 try:
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)
57 else:
58 cached = basedir.load_first_cache(config_site, 'interfaces', escape(url))
59 if cached:
60 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url, 'cached': cached})
61 return load_feed(cached, local = False)
62 else:
63 return None
64 except InvalidInterface as ex:
65 ex.feed_url = url
66 raise
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
72 @since 0.49
73 """
74 user = basedir.load_first_config(config_site, config_prog,
75 'feeds', model._pretty_escape(feed.url))
76 if user is None:
77 # For files saved by 0launch < 0.49
78 user = basedir.load_first_config(config_site, config_prog,
79 'user_overrides', escape(feed.url))
80 if not user:
81 return
83 try:
84 root = qdom.parse(file(user))
85 except Exception as ex:
86 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
87 raise
89 last_checked = root.getAttribute('last-checked')
90 if 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')
97 assert id is not None
98 impl = feed.implementations.get(id, None)
99 if not impl:
100 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': feed})
101 continue
103 user_stability = item.getAttribute('user-stability')
104 if 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))
115 if user is None:
116 # For files saved by 0launch < 0.49
117 user = basedir.load_first_config(config_site, config_prog,
118 'user_overrides', escape(interface.uri))
119 if not user:
120 return
122 try:
123 root = qdom.parse(file(user))
124 except Exception as ex:
125 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
126 raise
128 stability_policy = root.getAttribute('stability-policy')
129 if 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')
136 if not feed_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
143 @type feed_url: str
144 @param source: the name of the file to test
145 @type source: str
146 @return: the modification time in src (usually just the mtime of the file)
147 @rtype: int
148 @raise InvalidInterface: If the source's syntax is incorrect,
150 try:
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 as ex:
160 info(_("Error loading feed:\n"
161 "Interface URI: %(uri)s\n"
162 "Local file: %(source)s\n"
163 "%(exception)s") %
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.
169 Deprecated.
170 @param interface: the interface object to update
171 @type interface: L{model.Interface}
172 @param source: the name of the file to read
173 @type source: str
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)
182 if not 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
194 return feed
196 def load_feed(source, local = False, selections_ok = False, generate_sizes = False, implementation_id_alg=None, config=None):
197 """Load a feed from a local file.
198 @param source: the name of the file to read
199 @type source: str
200 @param local: this is a local feed
201 @type local: bool
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
210 @since: 0.48
211 @see: L{iface_cache.iface_cache}, which uses this to load the feeds"""
212 try:
213 root = qdom.parse(file(source))
214 except IOError as ex:
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 as ex:
219 raise InvalidInterface(_("Invalid XML"), ex)
221 if local:
222 if selections_ok and root.uri == XMLNS_IFACE and root.name == 'selections':
223 from zeroinstall.injector import selections
224 return selections.Selections(root)
225 local_path = source
226 else:
227 local_path = None
228 if implementation_id_alg or generate_sizes:
229 from zeroinstall.injector.config import load_config
230 if config is None:
231 config = load_config()
232 feed = ZeroInstallFeed(root, local_path, None, generate_sizes, implementation_id_alg, config.fetcher, config.stores)
233 else:
234 feed = ZeroInstallFeed(root, local_path)
235 feed.last_modified = int(os.stat(source).st_mtime)
237 return feed