Added @tasks.async decorator to simplify common code.
[zeroinstall.git] / zeroinstall / 0launch-gui / bugs.py
blob429e3e1f259aa7e8a251259e686cb966988a62ce
1 # Copyright (C) 2007, Thomas Leonard
2 # See http://0install.net/0compile.html
4 import sys, os
5 import gtk, pango
6 import dialog
7 import logging
9 import zeroinstall
10 from zeroinstall import support
12 def report_bug(policy, iface):
13 assert iface
15 # TODO: Check the interface to decide where to send bug reports
17 issue_file = '/etc/issue'
18 if os.path.exists(issue_file):
19 issue = file(issue_file).read().strip()
20 else:
21 issue = "(file '%s' not found)" % issue
23 text = 'Problem with %s\n' % iface.uri
24 if iface.uri != policy.root:
25 text = ' (while attempting to run %s)\n' % policy.root
26 text += '\n'
28 text += 'Zero Install: Version %s, with Python %s\n' % (zeroinstall.version, sys.version)
30 text += '\nChosen implementations:\n'
32 if not policy.ready:
33 text += ' Failed to select all required implementations\n'
35 for chosen_iface in policy.implementation:
36 text += '\n Interface: %s\n' % chosen_iface.uri
37 impl = policy.implementation[chosen_iface]
38 if impl:
39 text += ' Version: %s\n' % impl.get_version()
40 if impl.interface != chosen_iface:
41 text += ' From feed: %s\n' % impl.interface.uri
42 text += ' ID: %s\n' % impl.id
43 else:
44 text += ' No implementation selected\n'
46 text += '\nSystem:\n %s\n\nIssue:\n %s\n' % ('\n '.join(os.uname()), issue)
48 reporter = BugReporter(policy, iface, text)
49 reporter.show()
51 class BugReporter(dialog.Dialog):
52 def __init__(self, policy, iface, env):
53 dialog.Dialog.__init__(self)
54 self.set_title('Report a Bug')
55 self.set_modal(True)
56 self.set_has_separator(False)
57 self.policy = policy
58 self.frames = []
60 vbox = gtk.VBox(False, 4)
61 vbox.set_border_width(10)
62 self.vbox.pack_start(vbox, True, True, 0)
64 self.set_default_size(gtk.gdk.screen_width() / 2, -1)
66 def frame(title, contents, buffer):
67 fr = gtk.Frame()
68 label = gtk.Label('')
69 label.set_markup('<b>%s</b>' % title)
70 fr.set_label_widget(label)
71 fr.set_shadow_type(gtk.SHADOW_NONE)
72 vbox.pack_start(fr, True, True, 0)
74 align = gtk.Alignment(0, 0, 1, 1)
75 align.set_padding(0, 0, 16, 0)
76 fr.add(align)
77 align.add(contents)
79 self.frames.append((title, buffer))
81 def text_area(text = None, mono = False):
82 swin = gtk.ScrolledWindow()
83 swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
84 swin.set_shadow_type(gtk.SHADOW_IN)
86 tv = gtk.TextView()
87 tv.set_wrap_mode(gtk.WRAP_WORD)
88 swin.add(tv)
89 if text:
90 tv.get_buffer().insert_at_cursor(text)
92 if mono:
93 tv.modify_font(pango.FontDescription('mono'))
95 tv.set_accepts_tab(False)
97 return swin, tv.get_buffer()
99 actual = text_area()
100 frame("What doesn't work?", *actual)
102 expected = text_area()
103 frame('What did you expect to happen?', *expected)
105 errors_box = gtk.VBox(False, 0)
106 errors_swin, errors_buffer = text_area(mono = True)
107 errors_box.pack_start(errors_swin, True, True, 0)
108 buttons = gtk.HButtonBox()
109 buttons.set_layout(gtk.BUTTONBOX_START)
110 errors_box.pack_start(buttons, False, True, 4)
111 get_errors = gtk.Button('Run it now and record the output')
112 get_errors.connect('clicked', lambda button: self.collect_output(errors_buffer))
113 buttons.add(get_errors)
115 frame('Are any errors or warnings displayed?', errors_box, errors_buffer)
117 if dialog.last_error:
118 errors_buffer.insert_at_cursor(str(dialog.last_error))
120 environ = text_area(env, mono = True)
121 frame('Information about your setup', *environ)
123 self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
124 self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
125 self.set_default_response(gtk.RESPONSE_OK)
127 def resp(box, r):
128 if r == gtk.RESPONSE_OK:
129 text = ''
130 for title, buffer in self.frames:
131 start = buffer.get_start_iter()
132 end = buffer.get_end_iter()
133 text += '%s\n\n%s\n\n' % (title, buffer.get_text(start, end).strip())
134 title = 'Bug for %s' % iface.get_name()
135 self.report_bug(title, text)
136 self.destroy()
137 dialog.alert(self, "Your bug report has been sent. Thank you.",
138 type = gtk.MESSAGE_INFO)
139 else:
140 self.destroy()
141 self.connect('response', resp)
143 self.show_all()
145 def collect_output(self, buffer):
146 import logging
147 from zeroinstall.injector import run
149 # TODO: send request to 0launch instead of running it ourselves
150 # because it should run inside any sandbox being used
152 iter = buffer.get_end_iter()
153 buffer.place_cursor(iter)
155 if not self.policy.ready:
156 missing = [iface.uri for iface in self.policy.implementation if self.policy.implementation[iface] is None]
157 buffer.insert_at_cursor("Can't run: no version has been selected for:\n- " +
158 "\n- ".join(missing))
159 return
160 uncached = self.policy.get_uncached_implementations()
161 if uncached:
162 buffer.insert_at_cursor("Can't run: the chosen versions have not been downloaded yet. I need:\n\n- " +
163 "\n\n- " . join(['%s version %s\n (%s)' %(x[0].uri, x[1].get_version(), x[1].id) for x in uncached]))
164 return
166 from zeroinstall.injector import selections
167 sels = selections.Selections(self.policy)
168 doc = sels.toDOM()
170 self.hide()
171 try:
172 gtk.gdk.flush()
173 iter = buffer.get_end_iter()
174 buffer.place_cursor(iter)
176 # Tell 0launch to run the program
177 doc.documentElement.setAttribute('run-test', 'true')
178 payload = doc.toxml('utf-8')
179 sys.stdout.write(('Length:%8x\n' % len(payload)) + payload)
180 sys.stdout.flush()
182 reply = support.read_bytes(0, len('Length:') + 9)
183 assert reply.startswith('Length:')
184 test_output = support.read_bytes(0, int(reply.split(':', 1)[1], 16))
186 # Cope with invalid UTF-8
187 import codecs
188 decoder = codecs.getdecoder('utf-8')
189 data = decoder(test_output, 'replace')[0]
191 buffer.insert_at_cursor(data)
192 finally:
193 self.show()
195 def report_bug(self, title, text):
196 try:
197 import urllib
198 from urllib2 import urlopen
200 stream = urlopen('http://sourceforge.net/tracker/index.php',
201 urllib.urlencode({
202 'group_id': '76468',
203 'atid': '929902',
204 'func': 'postadd',
205 'is_private': '0',
206 'summary': title,
207 'details': text}))
208 stream.read()
209 stream.close()
210 except:
211 # Write to stderr in the hope that it doesn't get lost
212 print >>sys.stderr, "Error sending bug report: %s\n\n%s" % (title, text)
213 raise