API changes: use Feed rather than Interface in many places
[zeroinstall/zeroinstall-afb.git] / zeroinstall / injector / reader.py
blob1cc151c6990f09cf5c73b4c9353e27210300636d
1 """
2 Parses an XML interface into a Python representation.
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, distro
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):
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 @rtype: bool"""
26 interface.reset()
27 from zeroinstall.injector.iface_cache import iface_cache
28 main_feed = iface_cache.get_feed(interface.uri, force = True)
30 # Add the distribution package manager's version, if any
31 path = basedir.load_first_data(config_site, 'native_feeds', model._pretty_escape(interface.uri))
32 if path:
33 # Resolve any symlinks
34 info(_("Adding native packager feed '%s'"), path)
35 interface.extra_feeds.append(Feed(os.path.realpath(path), None, False))
37 update_user_overrides(interface, main_feed)
39 return main_feed is not None
41 def load_feed_from_cache(url):
42 """Load a feed. If the feed is remote, load from the cache. If local, load it directly.
43 @return the feed, or None if it's remote and not cached."""
44 if url.startswith('/'):
45 debug(_("Loading local feed file '%s'"), url)
46 return load_feed(url, local = True)
47 else:
48 cached = basedir.load_first_cache(config_site, 'interfaces', escape(url))
49 if cached:
50 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interface': url, 'cached': cached})
51 return load_feed(cached, local = False)
52 else:
53 return None
55 def update_user_overrides(interface, main_feed = None):
56 """Update an interface with user-supplied information.
57 @param interface: the interface object to update
58 @type interface: L{model.Interface}
59 @param main_feed: feed to update with last_checked information
60 @note: feed updates shouldn't really be here. main_feed may go away in future.
61 """
62 user = basedir.load_first_config(config_site, config_prog,
63 'user_overrides', escape(interface.uri))
64 if not user:
65 return
67 try:
68 root = qdom.parse(file(user))
69 except Exception, ex:
70 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
71 raise
73 # This is a bit wrong; this information is about the feed,
74 # not the interface.
75 if main_feed:
76 last_checked = root.getAttribute('last-checked')
77 if last_checked:
78 main_feed.last_checked = int(last_checked)
80 stability_policy = root.getAttribute('stability-policy')
81 if stability_policy:
82 interface.set_stability_policy(stability_levels[str(stability_policy)])
84 for item in root.childNodes:
85 if item.uri != XMLNS_IFACE: continue
86 if item.name == 'implementation':
87 id = item.getAttribute('id')
88 assert id is not None
89 if not (id.startswith('/') or id.startswith('.') or id.startswith('package:')):
90 assert '=' in id
91 if main_feed:
92 impl = main_feed.implementations.get(id, None)
93 else:
94 impl = None
95 if not impl:
96 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': interface})
97 continue
99 user_stability = item.getAttribute('user-stability')
100 if user_stability:
101 impl.user_stability = stability_levels[str(user_stability)]
102 elif item.name == 'feed':
103 feed_src = item.getAttribute('src')
104 if not feed_src:
105 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
106 interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs')))
108 def check_readable(feed_url, source):
109 """Test whether a feed file is valid.
110 @param feed_url: the feed's expected URL
111 @type feed_url: str
112 @param source: the name of the file to test
113 @type source: str
114 @return: the modification time in src (usually just the mtime of the file)
115 @rtype: int
116 @raise InvalidInterface: If the source's syntax is incorrect,
118 try:
119 feed = load_feed(source, local = False)
121 if feed.url != feed_url:
122 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
123 "%(feed_url)s is given in the feed, but\n"
124 "%(interface_uri)s was requested") %
125 {'feed_url': feed.url, 'interface_uri': feed_url})
126 return feed.last_modified
127 except InvalidInterface, ex:
128 info(_("Error loading feed:\n"
129 "Interface URI: %(uri)s\n"
130 "Local file: %(source)s\n"
131 "%(exception)s") %
132 {'uri': feed_url, 'source': source, 'exception': ex})
133 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': feed_url, 'exception': ex})
135 def update(interface, source, local = False):
136 """Read in information about an interface.
137 Deprecated.
138 @param interface: the interface object to update
139 @type interface: L{model.Interface}
140 @param source: the name of the file to read
141 @type source: str
142 @param local: use file's mtime for last-modified, and uri attribute is ignored
143 @raise InvalidInterface: if the source's syntax is incorrect
144 @return: the new feed (since 0.32)
145 @see: L{update_from_cache}, which calls this"""
146 assert isinstance(interface, Interface)
148 feed = load_feed(source, local)
150 if not local:
151 if feed.url != interface.uri:
152 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
153 "%(feed_url)s is given in the feed, but\n"
154 "%(interface_uri)s was requested") %
155 {'feed_url': feed.url, 'interface_uri': interface.uri})
157 # Hack.
158 from zeroinstall.injector.iface_cache import iface_cache
159 iface_cache._feeds[unicode(interface.uri)] = feed
161 return feed
163 def load_feed(source, local = False):
164 """Load a feed from a local file.
165 @param source: the name of the file to read
166 @type source: str
167 @param local: this is a local feed
168 @type local: bool
169 @raise InvalidInterface: if the source's syntax is incorrect
170 @return: the new feed
171 @since: 0.48
172 @see: L{iface_cache.get_feed}, which calls this"""
173 try:
174 root = qdom.parse(file(source))
175 except IOError, ex:
176 if ex.errno == 2:
177 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)
178 raise InvalidInterface(_("Can't read file"), ex)
179 except Exception, ex:
180 raise InvalidInterface(_("Invalid XML"), ex)
182 if local:
183 local_path = source
184 else:
185 local_path = None
186 feed = ZeroInstallFeed(root, local_path, distro.get_host_distribution())
187 feed.last_modified = int(os.stat(source).st_mtime)
188 return feed