Package zeroinstall :: Module helpers
[frames] | no frames]

Source Code for Module zeroinstall.helpers

  1  """ 
  2  Convenience routines for performing common operations. 
  3  @since: 0.28 
  4  """ 
  5   
  6  # Copyright (C) 2009, Thomas Leonard 
  7  # See the README file for details, or visit http://0install.net. 
  8   
  9  from __future__ import print_function 
 10   
 11  import os, sys 
 12  from zeroinstall import support, SafeException, logger 
 13  from zeroinstall.support import tasks 
 14   
 15  DontUseGUI = object() 
 16   
17 -def get_selections_gui(iface_uri, gui_args, test_callback = None, use_gui = True):
18 """Run the GUI to choose and download a set of implementations. 19 The user may ask the GUI to submit a bug report about the program. In that case, 20 the GUI may ask us to test it. test_callback is called in that case with the implementations 21 to be tested; the callback will typically call L{zeroinstall.injector.run.test_selections} and return the result of that. 22 @param iface_uri: the required program, or None to show just the preferences dialog 23 @type iface_uri: str 24 @param gui_args: any additional arguments for the GUI itself 25 @type gui_args: [str] 26 @param test_callback: function to use to try running the program 27 @type test_callback: L{zeroinstall.injector.selections.Selections} -> str 28 @param use_gui: if True, raise a SafeException if the GUI is not available. If None, returns DontUseGUI if the GUI cannot be started. If False, returns DontUseGUI always. (since 1.11) 29 @type use_gui: bool | None 30 @return: the selected implementations 31 @rtype: L{zeroinstall.injector.selections.Selections} 32 @since: 0.28""" 33 if use_gui is False: 34 return DontUseGUI 35 36 if not os.environ.get('DISPLAY', None): 37 if use_gui is None: 38 return DontUseGUI 39 else: 40 raise SafeException("Can't use GUI because $DISPLAY is not set") 41 42 from zeroinstall.injector import selections, qdom 43 from io import BytesIO 44 45 from os.path import join, dirname 46 gui_exe = join(dirname(__file__), '0launch-gui', '0launch-gui') 47 48 import socket 49 cli, gui = socket.socketpair() 50 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 if use_gui is True: 62 gui_args = ['-g'] + gui_args 63 if iface_uri is not None: 64 gui_args = gui_args + ['--', iface_uri] 65 os.execvp(sys.executable, [sys.executable, gui_exe] + gui_args) 66 except: 67 import traceback 68 traceback.print_exc(file = sys.stderr) 69 finally: 70 sys.stderr.flush() 71 os._exit(1) 72 # We are the parent (CLI) 73 gui.close() 74 gui = None 75 76 while True: 77 logger.info("Waiting for selections from GUI...") 78 79 reply = support.read_bytes(cli.fileno(), len('Length:') + 9, null_ok = True) 80 if reply: 81 if not reply.startswith(b'Length:'): 82 raise Exception("Expected Length:, but got %s" % repr(reply)) 83 reply = reply.decode('ascii') 84 xml = support.read_bytes(cli.fileno(), int(reply.split(':', 1)[1], 16)) 85 86 dom = qdom.parse(BytesIO(xml)) 87 sels = selections.Selections(dom) 88 89 if dom.getAttribute('run-test'): 90 logger.info("Testing program, as requested by GUI...") 91 if test_callback is None: 92 output = b"Can't test: no test_callback was passed to get_selections_gui()\n" 93 else: 94 output = test_callback(sels) 95 logger.info("Sending results to GUI...") 96 output = ('Length:%8x\n' % len(output)).encode('utf-8') + output 97 logger.debug("Sending: %s", repr(output)) 98 while output: 99 sent = cli.send(output) 100 output = output[sent:] 101 continue 102 else: 103 sels = None 104 105 pid, status = os.waitpid(child, 0) 106 assert pid == child 107 if status == 1 << 8: 108 logger.info("User cancelled the GUI; aborting") 109 return None # Aborted 110 elif status == 100 << 8: 111 if use_gui is None: 112 return DontUseGUI 113 else: 114 raise SafeException("No GUI available") 115 if status != 0: 116 raise Exception("Error from GUI: code = %d" % status) 117 break 118 finally: 119 for sock in [cli, gui]: 120 if sock is not None: sock.close() 121 122 return sels
123
124 -def ensure_cached(uri, command = 'run', config = None):
125 """Ensure that an implementation of uri is cached. 126 If not, it downloads one. It uses the GUI if a display is 127 available, or the console otherwise. 128 @param uri: the required interface 129 @type uri: str 130 @type command: str 131 @return: the selected implementations, or None if the user cancelled 132 @rtype: L{zeroinstall.injector.selections.Selections}""" 133 from zeroinstall.injector.driver import Driver 134 135 if config is None: 136 from zeroinstall.injector.config import load_config 137 config = load_config() 138 139 from zeroinstall.injector.requirements import Requirements 140 requirements = Requirements(uri) 141 requirements.command = command 142 143 d = Driver(config, requirements) 144 145 if d.need_download() or not d.solver.ready: 146 sels = get_selections_gui(uri, ['--command', command], use_gui = None) 147 if sels != DontUseGUI: 148 return sels 149 done = d.solve_and_download_impls() 150 tasks.wait_for_blocker(done) 151 152 return d.solver.selections
153
154 -def exec_man(stores, sels, main = None, fallback_name = None):
155 """Exec the man command to show the man-page for this interface. 156 Never returns. 157 @type stores: L{zeroinstall.zerostore.Stores} 158 @type sels: L{zeroinstall.injector.selections.Selections} 159 @type main: str | None 160 @type fallback_name: str | None 161 @since: 1.12""" 162 interface_uri = sels.interface 163 selected_impl = sels.selections[interface_uri] 164 165 if selected_impl.id.startswith('package'): 166 impl_path = None 167 else: 168 impl_path = selected_impl.get_path(stores) 169 170 if main is None: 171 if sels.commands: 172 selected_command = sels.commands[0] 173 else: 174 print("No <command> in selections!", file=sys.stderr) 175 sys.exit(1) 176 main = selected_command.path 177 if main is None: 178 print("No main program for interface '%s'" % interface_uri, file=sys.stderr) 179 sys.exit(1) 180 181 prog_name = os.path.basename(main) 182 183 if impl_path is None: 184 # Package implementation 185 logger.debug("Searching for man-page native command %s (from %s)" % (prog_name, fallback_name)) 186 os.execlp('man', 'man', prog_name) 187 188 assert impl_path 189 190 logger.debug("Searching for man-page for %s or %s in %s" % (prog_name, fallback_name, impl_path)) 191 192 # TODO: the feed should say where the man-pages are, but for now we'll accept 193 # a directory called man in some common locations... 194 for mandir in ['man', 'share/man', 'usr/man', 'usr/share/man']: 195 manpath = os.path.join(impl_path, mandir) 196 if os.path.isdir(manpath): 197 # Note: unlike "man -M", this also copes with LANG settings... 198 os.environ['MANPATH'] = manpath 199 os.execlp('man', 'man', prog_name) 200 sys.exit(1) 201 202 # No man directory given or found, so try searching for man files 203 204 manpages = [] 205 for root, dirs, files in os.walk(impl_path): 206 for f in files: 207 if f.endswith('.gz'): 208 manpage_file = f[:-3] 209 else: 210 manpage_file = f 211 if manpage_file.endswith('.1') or \ 212 manpage_file.endswith('.6') or \ 213 manpage_file.endswith('.8'): 214 manpage_prog = manpage_file[:-2] 215 if manpage_prog == prog_name or manpage_prog == fallback_name: 216 os.execlp('man', 'man', os.path.join(root, f)) 217 sys.exit(1) 218 else: 219 manpages.append((root, f)) 220 for d in list(dirs): 221 if d.startswith('.'): 222 dirs.remove(d) 223 224 print("No matching manpage was found for '%s' (%s)" % (fallback_name, interface_uri)) 225 if manpages: 226 print("These non-matching man-pages were found, however:") 227 for root, file in manpages: 228 print(os.path.join(root, file)) 229 sys.exit(1)
230