Test opening /dev/null for missing FDs. Removed unnecessary fstat().
[zeroinstall.git] / zeroinstall / injector / cli.py
blob783803897d20fda70ca2beb637fde32cec87647e
1 """
2 The B{0launch} command-line interface.
4 This code is here, rather than in B{0launch} itself, simply so that it gets byte-compiled at
5 install time.
6 """
8 import os, sys
9 from optparse import OptionParser
10 import logging
12 #def program_log(msg): os.access('MARK: 0launch: ' + msg, os.F_OK)
13 #import __main__
14 #__main__.__builtins__.program_log = program_log
15 #program_log('0launch ' + ' '.join((sys.argv[1:])))
17 def main(command_args):
18 """Act as if 0launch was run with the given arguments.
19 @arg command_args: array of arguments (e.g. C{sys.argv[1:]})
20 @type command_args: [str]
21 """
22 # Ensure stdin, stdout and stderr FDs exist, to avoid confusion
23 for std in (0, 1, 2):
24 try:
25 os.fstat(std)
26 except OSError:
27 fd = os.open('/dev/null', os.O_RDONLY)
28 if fd != std:
29 os.dup2(fd, std)
30 os.close(fd)
32 parser = OptionParser(usage="usage: %prog [options] interface [args]\n"
33 " %prog --list [search-term]\n"
34 " %prog --import [signed-interface-files]\n"
35 " %prog --feed [interface]")
36 parser.add_option("", "--before", help="choose a version before this", metavar='VERSION')
37 parser.add_option("-c", "--console", help="never use GUI", action='store_false', dest='gui')
38 parser.add_option("-d", "--download-only", help="fetch but don't run", action='store_true')
39 parser.add_option("-D", "--dry-run", help="just print actions", action='store_true')
40 parser.add_option("-f", "--feed", help="add or remove a feed", action='store_true')
41 parser.add_option("-g", "--gui", help="show graphical policy editor", action='store_true')
42 parser.add_option("-i", "--import", help="import from files, not from the network", action='store_true')
43 parser.add_option("-l", "--list", help="list all known interfaces", action='store_true')
44 parser.add_option("-m", "--main", help="name of the file to execute")
45 parser.add_option("", "--not-before", help="minimum version to choose", metavar='VERSION')
46 parser.add_option("-o", "--offline", help="try to avoid using the network", action='store_true')
47 parser.add_option("-r", "--refresh", help="refresh all used interfaces", action='store_true')
48 parser.add_option("-s", "--source", help="select source code", action='store_true')
49 parser.add_option("-v", "--verbose", help="more verbose output", action='count')
50 parser.add_option("-V", "--version", help="display version information", action='store_true')
51 parser.disable_interspersed_args()
53 (options, args) = parser.parse_args(command_args)
55 if options.verbose:
56 logger = logging.getLogger()
57 if options.verbose == 1:
58 logger.setLevel(logging.INFO)
59 else:
60 logger.setLevel(logging.DEBUG)
62 from zeroinstall.injector import model, download, autopolicy, namespaces
64 if options.list:
65 from zeroinstall.injector.iface_cache import iface_cache
66 if len(args) == 0:
67 match = None
68 matches = iface_cache.list_all_interfaces()
69 elif len(args) == 1:
70 match = args[0].lower()
71 matches = [i for i in iface_cache.list_all_interfaces() if match in i.lower()]
72 else:
73 parser.print_help()
74 sys.exit(1)
76 matches.sort()
77 for i in matches:
78 print i
79 sys.exit(0)
81 if options.version:
82 import zeroinstall
83 print "0launch (zero-install) " + zeroinstall.version
84 print "Copyright (C) 2006 Thomas Leonard"
85 print "This program comes with ABSOLUTELY NO WARRANTY,"
86 print "to the extent permitted by law."
87 print "You may redistribute copies of this program"
88 print "under the terms of the GNU General Public License."
89 print "For more information about these matters, see the file named COPYING."
90 sys.exit(0)
92 if len(args) < 1:
93 if options.gui:
94 args = [namespaces.injector_gui_uri]
95 options.download_only = True
96 else:
97 parser.print_help()
98 sys.exit(1)
100 try:
101 if getattr(options, 'import'):
102 from zeroinstall.injector import gpg, handler
103 from zeroinstall.injector.iface_cache import iface_cache, PendingFeed
104 from xml.dom import minidom
105 for x in args:
106 if not os.path.isfile(x):
107 raise model.SafeException("File '%s' does not exist" % x)
108 logging.info("Importing from file '%s'", x)
109 signed_data = file(x)
110 data, sigs = gpg.check_stream(signed_data)
111 doc = minidom.parseString(data.read())
112 uri = doc.documentElement.getAttribute('uri')
113 if not uri:
114 raise model.SafeException("Missing 'uri' attribute on root element in '%s'" % x)
115 iface = iface_cache.get_interface(uri)
116 logging.info("Importing information about interface %s", iface)
117 signed_data.seek(0)
118 pending = PendingFeed(uri, signed_data)
119 iface_cache.add_pending(pending)
121 def keys_ready():
122 if not iface_cache.update_interface_if_trusted(iface, pending.sigs, pending.new_xml):
123 handler.confirm_trust_keys(iface, pending.sigs, pending.new_xml)
125 handler = handler.Handler()
126 pending.begin_key_downloads(handler, keys_ready)
127 handler.wait_for_downloads()
129 sys.exit(0)
131 if getattr(options, 'feed'):
132 from zeroinstall.injector import iface_cache, writer
133 from xml.dom import minidom
134 for x in args:
135 print "Feed '%s':\n" % x
136 x = model.canonical_iface_uri(x)
137 policy = autopolicy.AutoPolicy(x, download_only = True, dry_run = options.dry_run)
138 if options.offline:
139 policy.network_use = model.network_offline
140 policy.recalculate_with_dl()
141 interfaces = policy.get_feed_targets(policy.root)
142 for i in range(len(interfaces)):
143 feed = interfaces[i].get_feed(x)
144 if feed:
145 print "%d) Remove as feed for '%s'" % (i + 1, interfaces[i].uri)
146 else:
147 print "%d) Add as feed for '%s'" % (i + 1, interfaces[i].uri)
148 print
149 while True:
150 try:
151 i = raw_input('Enter a number, or CTRL-C to cancel [1]: ').strip()
152 except KeyboardInterrupt:
153 print
154 raise model.SafeException("Aborted at user request.")
155 if i == '':
156 i = 1
157 else:
158 try:
159 i = int(i)
160 except ValueError:
161 i = 0
162 if i > 0 and i <= len(interfaces):
163 break
164 print "Invalid number. Try again. (1 to %d)" % len(interfaces)
165 iface = interfaces[i - 1]
166 feed = iface.get_feed(x)
167 if feed:
168 iface.feeds.remove(feed)
169 else:
170 iface.feeds.append(model.Feed(x, arch = None, user_override = True))
171 writer.save_interface(iface)
172 print "\nFeed list for interface '%s' is now:" % iface.get_name()
173 if iface.feeds:
174 for f in iface.feeds:
175 print "- " + f.uri
176 else:
177 print "(no feeds)"
178 sys.exit(0)
180 iface_uri = model.canonical_iface_uri(args[0])
182 # Singleton instance used everywhere...
183 policy = autopolicy.AutoPolicy(iface_uri,
184 download_only = bool(options.download_only),
185 dry_run = options.dry_run,
186 src = options.source)
188 if options.before or options.not_before:
189 policy.root_restrictions.append(model.Restriction(model.parse_version(options.before),
190 model.parse_version(options.not_before)))
192 if options.offline:
193 policy.network_use = model.network_offline
195 # Note that need_download() triggers a recalculate()
196 if options.refresh or options.gui:
197 # We could run immediately, but the user asked us not to
198 can_run_immediately = False
199 else:
200 can_run_immediately = (not policy.need_download()) and policy.ready
202 if options.download_only and policy.stale_feeds:
203 can_run_immediately = False
205 if can_run_immediately:
206 if policy.stale_feeds:
207 # There are feeds we should update, but we can run without them.
208 # Do the update in the background while the program is running.
209 import background
210 background.spawn_background_update(policy, options.verbose > 0)
211 policy.execute(args[1:], main = options.main)
212 assert options.dry_run or options.download_only
213 return
215 # If the user didn't say whether to use the GUI, choose for them.
216 if options.gui is None and os.environ.get('DISPLAY', None):
217 options.gui = True
218 # If we need to download anything, we might as well
219 # refresh all the interfaces first. Also, this triggers
220 # the 'checking for updates' box, which is non-interactive
221 # when there are no changes to the selection.
222 options.refresh = True
223 logging.info("Switching to GUI mode... (use --console to disable)")
224 except model.SafeException, ex:
225 if options.verbose: raise
226 print >>sys.stderr, ex
227 sys.exit(1)
229 if options.gui:
230 policy.set_root(namespaces.injector_gui_uri)
231 policy.src = False
233 # Try to start the GUI without using the network.
234 # The GUI can refresh itself if it wants to.
235 policy.freshness = 0
236 policy.network_use = model.network_offline
238 prog_args = [iface_uri] + args[1:]
239 # Options apply to actual program, not GUI
240 if options.download_only:
241 policy.download_only = False
242 prog_args.insert(0, '--download-only')
243 if options.refresh:
244 options.refresh = False
245 prog_args.insert(0, '--refresh')
246 if options.not_before:
247 prog_args.insert(0, options.not_before)
248 prog_args.insert(0, '--not-before')
249 if options.before:
250 prog_args.insert(0, options.before)
251 prog_args.insert(0, '--before')
252 if options.source:
253 prog_args.insert(0, '--source')
254 if options.main:
255 prog_args = ['--main', options.main] + prog_args
256 options.main = None
257 del policy.root_restrictions[:]
258 else:
259 prog_args = args[1:]
261 try:
262 #program_log('download_and_execute ' + iface_uri)
263 policy.download_and_execute(prog_args, refresh = bool(options.refresh), main = options.main)
264 except autopolicy.NeedDownload, ex:
265 print ex
266 sys.exit(0)
267 except model.SafeException, ex:
268 if options.verbose: raise
269 print >>sys.stderr, ex
270 sys.exit(1)