1 # Copyright (C) 2006, Thomas Leonard
2 # See the README file for details, or visit http://0install.net.
6 from logging
import info
, debug
, warn
7 from cStringIO
import StringIO
12 from namespaces
import *
16 from iface_cache
import iface_cache
17 from zeroinstall
import NeedDownload
20 __slots__
= ['root', 'implementation', 'watchers',
21 'help_with_testing', 'network_use',
22 'freshness', 'ready', 'handler', 'warned_offline']
24 def __init__(self
, root
, handler
= None):
26 self
.help_with_testing
= False
27 self
.network_use
= network_full
28 self
.freshness
= 60 * 60 * 24 * 30 # Seconds allowed since last update (1 month)
31 # If we need to download something but can't because we are offline,
32 # warn the user. But only the first time.
33 self
.warned_offline
= False
35 # (allow self for backwards compat)
36 self
.handler
= handler
or self
38 debug("Supported systems: '%s'", arch
.os_ranks
)
39 debug("Supported processors: '%s'", arch
.machine_ranks
)
41 path
= basedir
.load_first_config(config_site
, config_prog
, 'global')
44 config
= ConfigParser
.ConfigParser()
46 self
.help_with_testing
= config
.getboolean('global',
48 self
.network_use
= config
.get('global', 'network_use')
49 self
.freshness
= int(config
.get('global', 'freshness'))
50 assert self
.network_use
in network_levels
52 print >>sys
.stderr
, "Error loading config:", ex
56 iface_cache
.add_watcher(self
)
58 def set_root(self
, root
):
59 assert isinstance(root
, (str, unicode))
61 self
.implementation
= {} # Interface -> [Implementation | None]
63 def save_config(self
):
64 config
= ConfigParser
.ConfigParser()
65 config
.add_section('global')
67 config
.set('global', 'help_with_testing', self
.help_with_testing
)
68 config
.set('global', 'network_use', self
.network_use
)
69 config
.set('global', 'freshness', self
.freshness
)
71 path
= basedir
.save_config_path(config_site
, config_prog
)
72 path
= os
.path
.join(path
, 'global')
73 config
.write(file(path
+ '.new', 'w'))
74 os
.rename(path
+ '.new', path
)
76 def recalculate(self
):
77 self
.implementation
= {}
79 debug("Recalculate! root = %s", self
.root
)
81 if iface
in self
.implementation
:
82 debug("cycle; skipping second %s", iface
)
84 self
.implementation
[iface
] = None # Avoid cycles
86 impl
= self
._get
_best
_implementation
(iface
)
88 debug("Will use implementation %s (version %s)", impl
, impl
.get_version())
89 self
.implementation
[iface
] = impl
90 for d
in impl
.dependencies
.values():
91 debug("Considering dependency %s", d
)
92 process(self
.get_interface(d
.interface
))
94 debug("No implementation chould be chosen yet");
96 process(self
.get_interface(self
.root
))
97 for w
in self
.watchers
: w()
99 # Only to be called from recalculate, as it is quite slow.
100 # Use the results stored in self.implementation instead.
101 def _get_best_implementation(self
, iface
):
102 impls
= iface
.implementations
.values()
103 for f
in self
.usable_feeds(iface
):
104 debug("Processing feed %s", f
)
106 feed_iface
= self
.get_interface(f
.uri
)
107 if feed_iface
.name
and iface
.uri
not in feed_iface
.feed_for
:
108 warn("Missing <feed-for> for '%s' in '%s'",
110 if feed_iface
.implementations
:
111 impls
.extend(feed_iface
.implementations
.values())
112 except NeedDownload
, ex
:
114 except Exception, ex
:
115 warn("Failed to load feed %s for %s: %s",
118 debug("get_best_implementation(%s), with feeds: %s", iface
, iface
.feeds
)
121 info("Interface %s has no implementations!", iface
)
125 if self
.compare(iface
, x
, best
) < 0:
127 if self
.is_unusable(best
):
128 info("Best implementation of %s is %s, but unusable (%s)", iface
, best
,
129 self
.get_unusable_reason(best
))
133 def compare(self
, interface
, b
, a
):
134 a_stab
= a
.get_stability()
135 b_stab
= b
.get_stability()
137 # Usable ones come first
138 r
= cmp(self
.is_unusable(b
), self
.is_unusable(a
))
141 # Preferred versions come first
142 r
= cmp(a_stab
== preferred
, b_stab
== preferred
)
145 if self
.network_use
!= network_full
:
146 r
= cmp(self
.get_cached(a
), self
.get_cached(b
))
150 stab_policy
= interface
.stability_policy
152 if self
.help_with_testing
: stab_policy
= testing
153 else: stab_policy
= stable
155 if a_stab
>= stab_policy
: a_stab
= preferred
156 if b_stab
>= stab_policy
: b_stab
= preferred
158 r
= cmp(a_stab
, b_stab
)
161 # Newer versions come before older ones
162 r
= cmp(a
.version
, b
.version
)
166 r
= cmp(arch
.os_ranks
.get(a
.os
, None),
167 arch
.os_ranks
.get(b
.os
, None))
171 r
= cmp(arch
.machine_ranks
.get(a
.machine
, None),
172 arch
.machine_ranks
.get(b
.machine
, None))
175 # Slightly prefer cached versions
176 if self
.network_use
== network_full
:
177 r
= cmp(self
.get_cached(a
), self
.get_cached(b
))
180 return cmp(a
.id, b
.id)
182 def usable_feeds(self
, iface
):
183 """Generator for iface.feeds that are valid for our architecture."""
184 for f
in iface
.feeds
:
185 if f
.os
in arch
.os_ranks
and f
.machine
in arch
.machine_ranks
:
188 debug("Skipping '%s'; unsupported architecture %s-%s",
191 def get_ranked_implementations(self
, iface
):
192 impls
= iface
.implementations
.values()
193 for f
in self
.usable_feeds(iface
):
194 feed_iface
= self
.get_interface(f
.uri
)
195 if feed_iface
.implementations
:
196 impls
.extend(feed_iface
.implementations
.values())
197 impls
.sort(lambda a
, b
: self
.compare(iface
, a
, b
))
200 def is_unusable(self
, impl
):
201 return self
.get_unusable_reason(impl
) != None
203 def get_unusable_reason(self
, impl
):
204 """Returns the reason why this impl is unusable, or None if it's OK"""
205 stability
= impl
.get_stability()
206 if stability
<= buggy
:
207 return stability
.name
208 if self
.network_use
== network_offline
and not self
.get_cached(impl
):
209 return "Not cached and we are off-line"
210 if impl
.os
not in arch
.os_ranks
:
211 return "Unsupported OS"
212 if impl
.machine
not in arch
.machine_ranks
:
213 return "Unsupported machine type"
216 def get_interface(self
, uri
):
217 iface
= iface_cache
.get_interface(uri
)
219 if iface
.last_modified
is None:
220 if self
.network_use
!= network_offline
:
221 debug("Interface not cached and not off-line. Downloading...")
222 self
.begin_iface_download(iface
)
224 if self
.warned_offline
:
225 debug("Nothing known about interface, but we are off-line.")
228 info("Nothing known about interface '%s' and off-line. Trying feeds only.")
230 warn("Nothing known about interface '%s', but we are in off-line mode "
231 "(so not fetching)." % uri
)
232 self
.warned_offline
= True
233 elif not uri
.startswith('/'):
234 staleness
= time
.time() - (iface
.last_checked
or 0)
235 debug("Staleness for %s is %.2f hours", iface
, staleness
/ 3600.0)
237 if self
.network_use
!= network_offline
and self
.freshness
> 0 and staleness
> self
.freshness
:
238 debug("Updating %s", iface
)
239 self
.begin_iface_download(iface
, False)
240 #else: debug("Local interface, so not checking staleness.")
244 def begin_iface_download(self
, interface
, force
= False):
245 debug("begin_iface_download %s (force = %d)", interface
, force
)
246 if interface
.uri
.startswith('/'):
248 dl
= download
.begin_iface_download(interface
, force
)
251 debug("Already in progress")
254 debug("Need to download")
255 # Calls update_interface_from_network eventually on success
256 self
.handler
.monitor_download(dl
)
258 def get_implementation_path(self
, impl
):
259 assert isinstance(impl
, Implementation
)
260 if impl
.id.startswith('/'):
262 return iface_cache
.stores
.lookup(impl
.id)
264 def get_implementation(self
, interface
):
265 assert isinstance(interface
, Interface
)
267 if not interface
.name
and not interface
.feeds
:
268 raise SafeException("We don't have enough information to "
269 "run this program yet. "
270 "Need to download:\n%s" % interface
.uri
)
272 return self
.implementation
[interface
]
274 if interface
.implementations
:
276 if self
.network_use
== network_offline
:
277 offline
= "\nThis may be because 'Network Use' is set to Off-line."
278 raise SafeException("No usable implementation found for '%s'.%s" %
279 (interface
.name
, offline
))
282 def walk_interfaces(self
):
284 return iter(self
.implementation
)
286 def check_signed_data(self
, download
, signed_data
):
287 iface_cache
.check_signed_data(download
.interface
, signed_data
, self
.handler
)
289 def get_cached(self
, impl
):
290 if impl
.id.startswith('/'):
291 return os
.path
.exists(impl
.id)
294 path
= self
.get_implementation_path(impl
)
301 def add_to_cache(self
, source
, data
):
302 iface_cache
.add_to_cache(source
, data
)
304 def get_uncached_implementations(self
):
306 for iface
in self
.implementation
:
307 impl
= self
.implementation
[iface
]
309 if not self
.get_cached(impl
):
310 uncached
.append((iface
, impl
))
313 def refresh_all(self
, force
= True):
314 for x
in self
.walk_interfaces():
315 self
.begin_iface_download(x
, force
)
316 for f
in self
.usable_feeds(x
):
317 feed_iface
= self
.get_interface(f
.uri
)
318 self
.begin_iface_download(feed_iface
, force
)
320 def interface_changed(self
, interface
):
321 debug("interface_changed(%s): recalculating", interface
)
324 def get_feed_targets(self
, feed_iface_uri
):
325 """Return a list of Interfaces for which feed_iface can be a feed.
326 This is used by --feed. If there are no interfaces, raises SafeException."""
327 feed_iface
= self
.get_interface(feed_iface_uri
)
328 if not feed_iface
.feed_for
:
329 if not feed_iface
.name
:
330 raise SafeException("Can't get feed targets for '%s'; failed to load interface." %
332 raise SafeException("Missing <feed-for> element in '%s'; "
333 "this interface can't be used as a feed." % feed_iface_uri
)
334 feed_targets
= feed_iface
.feed_for
335 if not feed_iface
.name
:
336 warn("Warning: unknown interface '%s'" % feed_iface_uri
)
337 return [self
.get_interface(uri
) for uri
in feed_targets
]