If the GUI needs to be refreshed, pass the --refresh option so that only the
[zeroinstall.git] / zeroinstall / helpers.py
blob09a8e54b9b67fc21b797ff4be126a77fd11a9d26
1 """
2 Convenience routines for performing common operations.
3 @since: 0.28
4 """
6 # Copyright (C) 2007, Thomas Leonard
7 # See the README file for details, or visit http://0install.net.
9 import os, sys, logging
10 from zeroinstall import support
12 def get_selections_gui(iface_uri, gui_args, test_callback = None):
13 """Run the GUI to choose and download a set of implementations.
14 If the GUI itself is due for a check, refresh it first.
15 The user may ask the GUI to submit a bug report about the program. In that case,
16 the GUI may ask us to test it. test_callback is called in that case with the implementations
17 to be tested; the callback will typically call L{run.test_selections} and return the result of that.
18 @param iface_uri: the required program
19 @type iface_uri: str
20 @param gui_args: any additional arguments for the GUI itself
21 @type gui_args: [str]
22 @param test_callback: function to use to try running the program
23 @type test_callback: L{selections.Selections} -> str
24 @since: 0.28
25 """
26 from zeroinstall.injector import selections, autopolicy, namespaces, model, run, qdom
27 from StringIO import StringIO
29 gui_policy = autopolicy.AutoPolicy(namespaces.injector_gui_uri)
30 if iface_uri != namespaces.injector_gui_uri and (gui_policy.need_download() or gui_policy.stale_feeds):
31 # The GUI itself needs updating. Do that first.
32 logging.info("The GUI could do with updating first.")
33 gui_sel = get_selections_gui(namespaces.injector_gui_uri, ['--refresh'])
34 if gui_sel is None:
35 logging.info("Aborted at user request")
36 return None # Aborted by user
37 else:
38 # Try to start the GUI without using the network.
39 gui_policy.freshness = 0
40 gui_policy.network_use = model.network_offline
41 gui_policy.recalculate()
42 assert gui_policy.ready # Should always be some version available
43 gui_sel = selections.Selections(gui_policy)
45 cli_from_gui, gui_to_cli = os.pipe() # socket.socketpair() not in Python 2.3 :-(
46 gui_from_cli, cli_to_gui = os.pipe()
47 try:
48 child = os.fork()
49 if child == 0:
50 # We are the child
51 try:
52 try:
53 os.close(cli_from_gui)
54 os.close(cli_to_gui)
55 os.dup2(gui_to_cli, 1)
56 os.dup2(gui_from_cli, 0)
57 run.execute_selections(gui_sel, gui_args + ['--', iface_uri])
58 except:
59 import traceback
60 traceback.print_exc(file = sys.stderr)
61 finally:
62 sys.stderr.flush()
63 os._exit(1)
64 os.close(gui_from_cli)
65 gui_from_cli = None
66 os.close(gui_to_cli)
67 gui_to_cli = None
69 while True:
70 logging.info("Waiting for selections from GUI...")
72 reply = support.read_bytes(cli_from_gui, len('Length:') + 9, null_ok = True)
73 if reply:
74 if not reply.startswith('Length:'):
75 raise Exception("Expected Length:, but got %s" % repr(reply))
76 xml = support.read_bytes(cli_from_gui, int(reply.split(':', 1)[1], 16))
78 dom = qdom.parse(StringIO(xml))
79 sels = selections.Selections(dom)
81 if dom.getAttribute('run-test'):
82 logging.info("Testing program, as requested by GUI...")
83 if test_callback is None:
84 output = "Can't test: no test_callback was passed to get_selections_gui()\n"
85 else:
86 output = test_callback(sels)
87 logging.info("Sending results to GUI...")
88 output = ('Length:%8x\n' % len(output)) + output
89 logging.debug("Sending: %s" % `output`)
90 while output:
91 sent = os.write(cli_to_gui, output)
92 output = output[sent:]
93 continue
94 else:
95 sels = None
97 pid, status = os.waitpid(child, 0)
98 assert pid == child
99 if status == 1 << 8:
100 logging.info("User cancelled the GUI; aborting")
101 return None # Aborted
102 if status != 0:
103 raise Exception("Error from GUI: code = %d" % status)
104 break
105 finally:
106 for fd in [cli_to_gui, cli_from_gui, gui_to_cli, gui_from_cli]:
107 if fd is not None: os.close(fd)
109 return sels