If unpacking fails, report stderr from unpacker child process
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / helpers.py
blobd500dea4a89f9ce719114bc9fd62a3a629d0ef0c
1 """
2 Convenience routines for performing common operations.
3 @since: 0.28
4 """
6 # Copyright (C) 2009, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
9 import os, sys, logging
10 from zeroinstall import support
11 from zeroinstall import _
13 def get_selections_gui(iface_uri, gui_args, test_callback = None):
14 """Run the GUI to choose and download a set of implementations.
15 If the GUI itself is due for a check, refresh it first.
16 The user may ask the GUI to submit a bug report about the program. In that case,
17 the GUI may ask us to test it. test_callback is called in that case with the implementations
18 to be tested; the callback will typically call L{zeroinstall.injector.run.test_selections} and return the result of that.
19 @param iface_uri: the required program
20 @type iface_uri: str
21 @param gui_args: any additional arguments for the GUI itself
22 @type gui_args: [str]
23 @param test_callback: function to use to try running the program
24 @type test_callback: L{zeroinstall.injector.selections.Selections} -> str
25 @return: the selected implementations
26 @rtype: L{zeroinstall.injector.selections.Selections}
27 @since: 0.28
28 """
29 from zeroinstall.injector import selections, autopolicy, namespaces, model, run, qdom
30 from StringIO import StringIO
32 gui_policy = autopolicy.AutoPolicy(namespaces.injector_gui_uri)
33 if iface_uri != namespaces.injector_gui_uri and (gui_policy.need_download() or gui_policy.stale_feeds):
34 # The GUI itself needs updating. Do that first.
35 logging.info("The GUI could do with updating first.")
36 gui_sel = get_selections_gui(namespaces.injector_gui_uri, ['--refresh'])
37 if gui_sel is None:
38 logging.info("Aborted at user request")
39 return None # Aborted by user
40 else:
41 # Try to start the GUI without using the network.
42 gui_policy.freshness = 0
43 gui_policy.network_use = model.network_offline
44 gui_policy.recalculate()
45 assert gui_policy.ready # Should always be some version available
46 gui_sel = selections.Selections(gui_policy)
48 import socket
49 cli, gui = socket.socketpair()
51 try:
52 child = os.fork()
53 if child == 0:
54 # We are the child (GUI)
55 try:
56 try:
57 cli.close()
58 # We used to use pipes to support Python2.3...
59 os.dup2(gui.fileno(), 1)
60 os.dup2(gui.fileno(), 0)
61 run.execute_selections(gui_sel, gui_args + ['--', iface_uri])
62 except:
63 import traceback
64 traceback.print_exc(file = sys.stderr)
65 finally:
66 sys.stderr.flush()
67 os._exit(1)
68 # We are the parent (CLI)
69 gui.close()
70 gui = None
72 while True:
73 logging.info("Waiting for selections from GUI...")
75 reply = support.read_bytes(cli.fileno(), len('Length:') + 9, null_ok = True)
76 if reply:
77 if not reply.startswith('Length:'):
78 raise Exception("Expected Length:, but got %s" % repr(reply))
79 xml = support.read_bytes(cli.fileno(), int(reply.split(':', 1)[1], 16))
81 dom = qdom.parse(StringIO(xml))
82 sels = selections.Selections(dom)
84 if dom.getAttribute('run-test'):
85 logging.info("Testing program, as requested by GUI...")
86 if test_callback is None:
87 output = "Can't test: no test_callback was passed to get_selections_gui()\n"
88 else:
89 output = test_callback(sels)
90 logging.info("Sending results to GUI...")
91 output = ('Length:%8x\n' % len(output)) + output
92 logging.debug("Sending: %s", repr(output))
93 while output:
94 sent = cli.send(output)
95 output = output[sent:]
96 continue
97 else:
98 sels = None
100 pid, status = os.waitpid(child, 0)
101 assert pid == child
102 if status == 1 << 8:
103 logging.info("User cancelled the GUI; aborting")
104 return None # Aborted
105 if status != 0:
106 raise Exception("Error from GUI: code = %d" % status)
107 break
108 finally:
109 for sock in [cli, gui]:
110 if sock is not None: sock.close()
112 return sels
114 def ensure_cached(uri):
115 """Ensure that an implementation of uri is cached.
116 If not, it downloads one. It uses the GUI if a display is
117 available, or the console otherwise.
118 @param uri: the required interface
119 @type uri: str
120 @return: a new policy for this program, or None if the user cancelled
121 @rtype: L{zeroinstall.injector.selections.Selections}
123 from zeroinstall.injector import autopolicy, selections
125 p = autopolicy.AutoPolicy(uri, download_only = True)
126 p.freshness = 0 # Don't check for updates
128 if p.need_download() or not p.ready:
129 if os.environ.get('DISPLAY', None):
130 return get_selections_gui(uri, [])
131 else:
132 p.recalculate_with_dl()
133 p.start_downloading_impls()
134 p.handler.wait_for_downloads()
136 return selections.Selections(p)