Don't create .desktop filenames with spaces; xdg-desktop-menu gets confused
[zeroinstall/zeroinstall-mseaborn.git] / zeroinstall / injector / reader.py
blob6538eb4daecf5bc110108c2b42047c4f84f22375
1 """
2 Parses an XML interface into a Python representation.
3 """
5 # Copyright (C) 2006, Thomas Leonard
6 # See the README file for details, or visit http://0install.net.
8 import os
9 import sys
10 import shutil
11 import time
12 from logging import debug, warn, info
13 from os.path import dirname
15 from zeroinstall.support import basedir
16 from zeroinstall.injector import qdom, distro
17 from zeroinstall.injector.namespaces import *
18 from zeroinstall.injector.model import *
19 from zeroinstall.injector import model
20 from zeroinstall import version, SafeException
22 def update_from_cache(interface):
23 """Read a cached interface and any native feeds or user overrides.
24 @param interface: the interface object to update
25 @type interface: L{model.Interface}
26 @return: True if cached version and user overrides loaded OK.
27 False if upstream not cached. Local interfaces (starting with /) are
28 always considered to be cached, although they are not actually stored in the cache.
29 @rtype: bool"""
30 interface.reset()
31 main_feed = None
33 if interface.uri.startswith('/'):
34 debug("Loading local interface file '%s'", interface.uri)
35 update(interface, interface.uri, local = True)
36 cached = True
37 else:
38 cached = basedir.load_first_cache(config_site, 'interfaces', escape(interface.uri))
39 if cached:
40 debug("Loading cached information for %s from %s", interface, cached)
41 main_feed = update(interface, cached)
43 # Add the distribution package manager's version, if any
44 path = basedir.load_first_data(config_site, 'native_feeds', model._pretty_escape(interface.uri))
45 if path:
46 # Resolve any symlinks
47 info("Adding native packager feed '%s'", path)
48 interface.extra_feeds.append(Feed(os.path.realpath(path), None, False))
50 update_user_overrides(interface, main_feed)
52 # Special case: add our fall-back local copy of the injector as a feed
53 if interface.uri == injector_gui_uri:
54 local_gui = os.path.join(os.path.abspath(dirname(dirname(__file__))), '0launch-gui', 'ZeroInstall-GUI.xml')
55 interface.extra_feeds.append(Feed(local_gui, None, False))
57 return bool(cached)
59 def update_user_overrides(interface, main_feed = None):
60 """Update an interface with user-supplied information.
61 @param interface: the interface object to update
62 @type interface: L{model.Interface}
63 @param main_feed: feed to update with last_checked information
64 @note: feed updates shouldn't really be here. main_feed may go away in future.
65 """
66 user = basedir.load_first_config(config_site, config_prog,
67 'user_overrides', escape(interface.uri))
68 if not user:
69 return
71 root = qdom.parse(file(user))
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 impl = interface.implementations.get(id, None)
92 if not impl:
93 debug("Ignoring user-override for unknown implementation %s in %s", id, interface)
94 continue
96 user_stability = item.getAttribute('user-stability')
97 if user_stability:
98 impl.user_stability = stability_levels[str(user_stability)]
99 elif item.name == 'feed':
100 feed_src = item.getAttribute('src')
101 if not feed_src:
102 raise InvalidInterface('Missing "src" attribute in <feed>')
103 interface.extra_feeds.append(Feed(feed_src, item.getAttribute('arch'), True, langs = item.getAttribute('langs')))
105 def check_readable(interface_uri, source):
106 """Test whether an interface file is valid.
107 @param interface_uri: the interface's URI
108 @type interface_uri: str
109 @param source: the name of the file to test
110 @type source: str
111 @return: the modification time in src (usually just the mtime of the file)
112 @rtype: int
113 @raise InvalidInterface: If the source's syntax is incorrect,
115 tmp = Interface(interface_uri)
116 try:
117 update(tmp, source)
118 except InvalidInterface, ex:
119 info("Error loading feed:\n"
120 "Interface URI: %s\n"
121 "Local file: %s\n%s" %
122 (interface_uri, source, ex))
123 raise InvalidInterface("Error loading feed '%s':\n\n%s" % (interface_uri, ex))
124 return tmp.last_modified
126 def update(interface, source, local = False):
127 """Read in information about an interface.
128 @param interface: the interface object to update
129 @type interface: L{model.Interface}
130 @param source: the name of the file to read
131 @type source: str
132 @param local: use file's mtime for last-modified, and uri attribute is ignored
133 @raise InvalidInterface: if the source's syntax is incorrect
134 @return: the new feed (since 0.32)
135 @see: L{update_from_cache}, which calls this"""
136 assert isinstance(interface, Interface)
138 try:
139 root = qdom.parse(file(source))
140 except IOError, ex:
141 if ex.errno == 2:
142 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)
143 raise InvalidInterface("Can't read file", ex)
144 except Exception, ex:
145 raise InvalidInterface("Invalid XML", ex)
147 if local:
148 local_path = source
149 else:
150 local_path = None
151 feed = ZeroInstallFeed(root, local_path, distro.get_host_distribution())
152 feed.last_modified = int(os.stat(source).st_mtime)
154 if not local:
155 if feed.url != interface.uri:
156 raise InvalidInterface("Incorrect URL used for feed.\n\n"
157 "%s is given in the feed, but\n"
158 "%s was requested" %
159 (feed.url, interface.uri))
161 interface._main_feed = feed
162 return feed