Import _ into each module rather than using a builtin
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / injector / reader.py
blobd17f7e31af38b4e0fd060abe737dd6f0efa0f1a6
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
11 from os.path import dirname
13 from zeroinstall.support import basedir
14 from zeroinstall.injector import qdom, distro
15 from zeroinstall.injector.namespaces import config_site, config_prog, XMLNS_IFACE, injector_gui_uri
16 from zeroinstall.injector.model import Interface, InvalidInterface, ZeroInstallFeed, escape, Feed, stability_levels
17 from zeroinstall.injector import model
19 def update_from_cache(interface):
20 """Read a cached interface and any native feeds or user overrides.
21 @param interface: the interface object to update
22 @type interface: L{model.Interface}
23 @return: True if cached version and user overrides loaded OK.
24 False if upstream not cached. Local interfaces (starting with /) are
25 always considered to be cached, although they are not actually stored in the cache.
26 @rtype: bool"""
27 interface.reset()
28 main_feed = None
30 if interface.uri.startswith('/'):
31 debug(_("Loading local interface file '%s'"), interface.uri)
32 update(interface, interface.uri, local = True)
33 cached = True
34 else:
35 cached = basedir.load_first_cache(config_site, 'interfaces', escape(interface.uri))
36 if cached:
37 debug(_("Loading cached information for %(interface)s from %(cached)s"), {'interfaces': interface, 'cached': cached})
38 main_feed = update(interface, cached)
40 # Add the distribution package manager's version, if any
41 path = basedir.load_first_data(config_site, 'native_feeds', model._pretty_escape(interface.uri))
42 if path:
43 # Resolve any symlinks
44 info(_("Adding native packager feed '%s'"), path)
45 interface.extra_feeds.append(Feed(os.path.realpath(path), None, False))
47 update_user_overrides(interface, main_feed)
49 # Special case: add our fall-back local copy of the injector as a feed
50 if interface.uri == injector_gui_uri:
51 local_gui = os.path.join(os.path.abspath(dirname(dirname(__file__))), '0launch-gui', 'ZeroInstall-GUI.xml')
52 interface.extra_feeds.append(Feed(local_gui, None, False))
54 return bool(cached)
56 def update_user_overrides(interface, main_feed = None):
57 """Update an interface with user-supplied information.
58 @param interface: the interface object to update
59 @type interface: L{model.Interface}
60 @param main_feed: feed to update with last_checked information
61 @note: feed updates shouldn't really be here. main_feed may go away in future.
62 """
63 user = basedir.load_first_config(config_site, config_prog,
64 'user_overrides', escape(interface.uri))
65 if not user:
66 return
68 try:
69 root = qdom.parse(file(user))
70 except Exception, ex:
71 warn(_("Error reading '%(user)s': %(exception)s"), {'user': user, 'exception': ex})
72 raise
74 # This is a bit wrong; this information is about the feed,
75 # not the interface.
76 if main_feed:
77 last_checked = root.getAttribute('last-checked')
78 if last_checked:
79 main_feed.last_checked = int(last_checked)
81 stability_policy = root.getAttribute('stability-policy')
82 if stability_policy:
83 interface.set_stability_policy(stability_levels[str(stability_policy)])
85 for item in root.childNodes:
86 if item.uri != XMLNS_IFACE: continue
87 if item.name == 'implementation':
88 id = item.getAttribute('id')
89 assert id is not None
90 if not (id.startswith('/') or id.startswith('.') or id.startswith('package:')):
91 assert '=' in id
92 impl = interface.implementations.get(id, None)
93 if not impl:
94 debug(_("Ignoring user-override for unknown implementation %(id)s in %(interface)s"), {'id': id, 'interface': interface})
95 continue
97 user_stability = item.getAttribute('user-stability')
98 if user_stability:
99 impl.user_stability = stability_levels[str(user_stability)]
100 elif item.name == 'feed':
101 feed_src = item.getAttribute('src')
102 if not feed_src:
103 raise InvalidInterface(_('Missing "src" attribute in <feed>'))
104 interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs')))
106 def check_readable(interface_uri, source):
107 """Test whether an interface file is valid.
108 @param interface_uri: the interface's URI
109 @type interface_uri: str
110 @param source: the name of the file to test
111 @type source: str
112 @return: the modification time in src (usually just the mtime of the file)
113 @rtype: int
114 @raise InvalidInterface: If the source's syntax is incorrect,
116 tmp = Interface(interface_uri)
117 try:
118 update(tmp, source)
119 except InvalidInterface, ex:
120 info(_("Error loading feed:\n"
121 "Interface URI: %(uri)s\n"
122 "Local file: %(source)s\n"
123 "%(exception)s") %
124 {'uri': interface_uri, 'source': source, 'exception': ex})
125 raise InvalidInterface(_("Error loading feed '%(uri)s':\n\n%(exception)s") % {'uri': interface_uri, 'exception': ex})
126 return tmp.last_modified
128 def update(interface, source, local = False):
129 """Read in information about an interface.
130 @param interface: the interface object to update
131 @type interface: L{model.Interface}
132 @param source: the name of the file to read
133 @type source: str
134 @param local: use file's mtime for last-modified, and uri attribute is ignored
135 @raise InvalidInterface: if the source's syntax is incorrect
136 @return: the new feed (since 0.32)
137 @see: L{update_from_cache}, which calls this"""
138 assert isinstance(interface, Interface)
140 try:
141 root = qdom.parse(file(source))
142 except IOError, ex:
143 if ex.errno == 2:
144 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)
145 raise InvalidInterface(_("Can't read file"), ex)
146 except Exception, ex:
147 raise InvalidInterface(_("Invalid XML"), ex)
149 if local:
150 local_path = source
151 else:
152 local_path = None
153 feed = ZeroInstallFeed(root, local_path, distro.get_host_distribution())
154 feed.last_modified = int(os.stat(source).st_mtime)
156 if not local:
157 if feed.url != interface.uri:
158 raise InvalidInterface(_("Incorrect URL used for feed.\n\n"
159 "%(feed_url)s is given in the feed, but\n"
160 "%(interface_uri)s was requested") %
161 {'feed_url': feed.url, 'interface_uri': interface.uri})
163 interface._main_feed = feed
164 return feed